ruboto 0.5.4 → 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (41) hide show
  1. data/Gemfile.lock +9 -9
  2. data/README.md +2 -0
  3. data/Rakefile +71 -7
  4. data/assets/Rakefile +2 -407
  5. data/assets/libs/dexmaker20120305.jar +0 -0
  6. data/assets/rakelib/ruboto.rake +428 -0
  7. data/assets/samples/sample_broadcast_receiver.rb +7 -3
  8. data/assets/samples/sample_broadcast_receiver_test.rb +47 -1
  9. data/assets/src/RubotoActivity.java +6 -2
  10. data/assets/src/org/ruboto/EntryPointActivity.java +2 -2
  11. data/assets/src/org/ruboto/Script.java +91 -24
  12. data/assets/src/org/ruboto/test/ActivityTest.java +27 -24
  13. data/assets/src/org/ruboto/test/InstrumentationTestRunner.java +42 -8
  14. data/assets/src/ruboto/activity.rb +1 -1
  15. data/assets/src/ruboto/base.rb +17 -6
  16. data/assets/src/ruboto/generate.rb +458 -0
  17. data/assets/src/ruboto/legacy.rb +9 -12
  18. data/assets/src/ruboto/widget.rb +9 -1
  19. data/lib/java_class_gen/android_api.xml +1 -1
  20. data/lib/ruboto.rb +1 -2
  21. data/lib/ruboto/commands/base.rb +19 -4
  22. data/lib/ruboto/sdk_versions.rb +12 -0
  23. data/lib/ruboto/util/build.rb +10 -11
  24. data/lib/ruboto/util/update.rb +150 -51
  25. data/lib/ruboto/util/verify.rb +6 -4
  26. data/lib/ruboto/util/xml_element.rb +2 -2
  27. data/lib/ruboto/version.rb +1 -1
  28. data/test/activity/option_menu_activity.rb +5 -1
  29. data/test/activity/psych_activity.rb +11 -6
  30. data/test/activity/stack_activity_test.rb +13 -5
  31. data/test/app_test_methods.rb +4 -3
  32. data/test/broadcast_receiver_test.rb +86 -0
  33. data/test/minimal_app_test.rb +27 -19
  34. data/test/rake_test.rb +13 -2
  35. data/test/ruboto_gen_test.rb +17 -3
  36. data/test/ruboto_update_test.rb +24 -2
  37. data/test/service_test.rb +1 -1
  38. data/test/test_helper.rb +134 -62
  39. data/test/update_test_methods.rb +40 -14
  40. data/test/updated_example_test_methods.rb +41 -0
  41. metadata +12 -6
@@ -76,13 +76,24 @@ end
76
76
  # Import a class and set it up for handlers
77
77
  #
78
78
 
79
- def ruboto_import(package_class)
80
- klass = java_import(package_class) || eval("Java::#{package_class}")
81
- return unless klass
79
+ def ruboto_import(*package_classes)
80
+ already_classes = package_classes.select{|i| not i.is_a?(String) and not i.is_a?(Symbol)}
81
+ imported_classes = package_classes - already_classes
82
82
 
83
- klass.class_eval do
84
- extend Ruboto::CallbackClass
85
- include Ruboto::Callbacks
83
+ unless imported_classes.empty?
84
+ # TODO(uwe): The first part of this "if" is only needed for JRuby 1.6.x. Simplify when we stop supporting JRuby 1.6.x
85
+ if imported_classes.size == 1
86
+ imported_classes = [*(java_import(*imported_classes) || eval("Java::#{imported_classes[0]}"))]
87
+ else
88
+ imported_classes = java_import(imported_classes)
89
+ end
90
+ end
91
+
92
+ (already_classes + imported_classes).each do |package_class|
93
+ package_class.class_eval do
94
+ extend Ruboto::CallbackClass
95
+ include Ruboto::Callbacks
96
+ end
86
97
  end
87
98
  end
88
99
 
@@ -0,0 +1,458 @@
1
+ ######################################################
2
+ #
3
+ # generate.rb (by Scott Moyer)
4
+ #
5
+ # Uses the dexmaker project
6
+ # (http://code.google.com/p/dexmaker/)
7
+ # to generate Ruboto callbacks.
8
+ #
9
+ ######################################################
10
+
11
+ require 'ruboto/base'
12
+ require 'fileutils'
13
+
14
+ ######################################################
15
+ #
16
+ # Expand the functionality of TypeId
17
+ #
18
+
19
+ java_import 'com.google.dexmaker.TypeId'
20
+ class TypeId
21
+ @@convert_hash = {
22
+ nil => VOID,
23
+ "java.lang.String" => STRING,
24
+ "java.lang.Object" => OBJECT
25
+ }
26
+
27
+ @@corresponding_class = {
28
+ INT => get("Ljava/lang/Integer;"),
29
+ FLOAT => get("Ljava/lang/Float;"),
30
+ DOUBLE => get("Ljava/lang/Double;"),
31
+ BYTE => get("Ljava/lang/Byte;"),
32
+ BOOLEAN => get("Ljava/lang/Boolean;"),
33
+ CHAR => get("Ljava/lang/Char;"),
34
+ SHORT => get("Ljava/lang/Short;"),
35
+ LONG => get("Ljava/lang/Long;")
36
+ }
37
+
38
+ @@conversion_method = {}
39
+
40
+ %w(int float double byte boolean char short long).each do |i|
41
+ @@convert_hash[i] = const_get(i.upcase)
42
+ @@conversion_method[const_get(i.upcase)] = "#{i}Value"
43
+ end
44
+
45
+ def self.convert_type(type)
46
+ # TODO: Handling arrays
47
+ rv = @@convert_hash[type]
48
+ unless rv
49
+ rv = type.split("[")
50
+ rv[-1] = "L#{rv[-1].gsub('.', '/')};" unless rv[-1].length == 1
51
+ rv = get(rv.join("["))
52
+ end
53
+ rv
54
+ end
55
+
56
+ def corresponding_class
57
+ @@corresponding_class[self]
58
+ end
59
+
60
+ def conversion_method
61
+ @@conversion_method[self]
62
+ end
63
+
64
+ def primitive?
65
+ @@corresponding_class.key? self
66
+ end
67
+ end
68
+
69
+ ######################################################
70
+ #
71
+ # Helper methods for class generation
72
+ #
73
+
74
+ java_import 'com.google.dexmaker.Code'
75
+ class Code
76
+ def call_super(class_id, method_name, return_value, *parameters)
77
+ method_id = class_id.getMethod(return_value ? return_value.type : TypeId::VOID,
78
+ method_name, *(parameters.map{|i| i.type}))
79
+ invokeSuper(method_id, return_value, getThis(class_id), *parameters)
80
+ end
81
+ end
82
+
83
+ def create_constructor(dex_maker, super_class, class_id, *parameters)
84
+ constructor_id = class_id.getConstructor(*parameters)
85
+ dex_maker.declare(constructor_id, java.lang.reflect.Modifier::PUBLIC).instance_eval do
86
+ parameter_array = []
87
+ parameters.each_with_index{|param, i| parameter_array << getParameter(i, param)}
88
+
89
+ invokeDirect(super_class.getConstructor(*parameters), nil, getThis(class_id), *parameter_array)
90
+ returnVoid
91
+ end
92
+ end
93
+
94
+ ######################################################
95
+ #
96
+ # Generate a new classes for an interface or class
97
+ # Takes a hash
98
+ # :use_cache (default true) or regenerate
99
+ # :reload (default false) or use existing constant
100
+ # :dex_file - where to put the generated jar
101
+ # -- for single classes default to use package as path
102
+ # -- for multiple classes default to classes.jar
103
+ # The remaining key/value pairs represent source => new_class
104
+ # -- source is either a string or the actual class/interface
105
+ # -- new_class is a string "package.class"
106
+ #
107
+
108
+ def ruboto_generate(options)
109
+ use_cache = options.key?(:use_cache) ? options.delete(:use_cache) : true
110
+ reload = options.key?(:reload) ? options.delete(:reload) : false
111
+ dex_file = options.delete(:dex_file)
112
+
113
+ #
114
+ # Already loaded? Just check the first one.
115
+ #
116
+
117
+ class_name = options.values[0].split('.')[-1]
118
+ return Object.const_get(class_name) if Object.const_defined?(class_name) and not reload
119
+
120
+ #
121
+ # Set up directory
122
+ #
123
+
124
+ base_dir = "#{$activity.files_dir.absolute_path}/dx"
125
+ if dex_file
126
+ components = dex_file.split('/')
127
+ components.unshift(base_dir) unless components[0] == ""
128
+ dex_dir = components[0..-2].join('/')
129
+ dex_file = components[-1]
130
+ elsif options.size == 1
131
+ dex_dir = "#{base_dir}/#{options.values[0].split('.')[0..-2].join('/')}"
132
+ dex_file = "#{options.values[0].split('.')[-1]}.jar"
133
+ else
134
+ dex_dir = base_dir
135
+ dex_file = "classes.jar"
136
+ end
137
+ FileUtils.mkpath dex_dir unless File.exists?(dex_dir)
138
+ jar_file = java.io.File.new("#{dex_dir}/#{dex_file}")
139
+ puts "Exists: #{jar_file}" if File.exists?(jar_file.to_s)
140
+
141
+ #
142
+ # Already generated?
143
+ #
144
+
145
+ if use_cache
146
+ rv = ruboto_load_class(jar_file.path, *options.values)
147
+ return rv if rv
148
+ end
149
+
150
+ if File.exists? jar_file.path
151
+ File.delete jar_file.path
152
+ File.delete jar_file.path.gsub(/\.jar$/, ".dex")
153
+ end
154
+
155
+ puts "Generating: #{jar_file.path}"
156
+
157
+ dex_maker = com.google.dexmaker.DexMaker.new
158
+ options.each{|k, v| ruboto_generate_class(dex_maker, k, v)}
159
+
160
+ #
161
+ # Generate and save
162
+ #
163
+
164
+ dex = dex_maker.generate
165
+ jar_file.createNewFile
166
+ jarOut = java.util.jar.JarOutputStream.new(java.io.FileOutputStream.new(jar_file))
167
+ jarOut.putNextEntry(java.util.jar.JarEntry.new("classes.dex"))
168
+ jarOut.write(dex)
169
+ jarOut.closeEntry
170
+ jarOut.close
171
+
172
+ return ruboto_load_class(jar_file.path, *options.values)
173
+ end
174
+
175
+ ######################################################
176
+ #
177
+ # Open a dex jar and load the class(es)
178
+ #
179
+
180
+ def ruboto_load_class(file_name, *package_class_names)
181
+ return nil unless File.exists? file_name
182
+
183
+ loader = Java::dalvik.system.DexClassLoader.new(file_name, file_name.split('/')[0..-2].join('/'), nil,
184
+ com.google.dexmaker.DexMaker.java_class.class_loader)
185
+
186
+ runtime = org.jruby.Ruby.getGlobalRuntime
187
+
188
+ rv = []
189
+ package_class_names.each do |i|
190
+ tmp = org.jruby.javasupport.Java.getProxyClass(runtime,
191
+ org.jruby.javasupport.JavaClass.get(runtime, loader.loadClass(i)))
192
+ Object.const_set(i.split('.')[-1], tmp)
193
+ ruboto_import tmp
194
+ rv << tmp
195
+ end
196
+
197
+ rv.length == 1 ? rv[0] : rv
198
+ end
199
+
200
+ ######################################################
201
+ #
202
+ # Does the hard work of generating one class
203
+ #
204
+
205
+ def ruboto_generate_class(dex_maker, interface_or_class_name, package_class_name)
206
+ #
207
+ # Basic set up
208
+ #
209
+
210
+ if interface_or_class_name.is_a?(String)
211
+ interface_or_class = eval("Java::#{interface_or_class_name.gsub('$', '::')}")
212
+ else
213
+ interface_or_class = interface_or_class_name
214
+ interface_or_class_name = interface_or_class.java_class.name
215
+ end
216
+ interface_or_class_id = TypeId.convert_type(interface_or_class_name)
217
+ interface = interface_or_class.java_class.interface?
218
+ class_type_id = TypeId.convert_type(package_class_name)
219
+ parameters = [class_type_id, "#{package_class_name.split('.')[-1]}.generated", java.lang.reflect.Modifier::PUBLIC,
220
+ interface ? TypeId::OBJECT : interface_or_class_id]
221
+ parameters << TypeId.convert_type(interface_or_class_name) if interface
222
+ dex_maker.declare(*parameters)
223
+
224
+ #
225
+ # Create callbacks field
226
+ #
227
+
228
+ callbackProcs_field = class_type_id.getField(TypeId.get("[Ljava/lang/Object;"), "callbackProcs")
229
+ dex_maker.declare(callbackProcs_field, java.lang.reflect.Modifier::PRIVATE, nil)
230
+
231
+ #
232
+ # Build constructor and create callbacks array
233
+ #
234
+
235
+ if interface
236
+ create_constructor dex_maker, TypeId::OBJECT, class_type_id
237
+ else
238
+ interface_or_class.java_class.constructors.each do |c|
239
+ parameter_type_array = c.parameter_types.map{|p| TypeId.convert_type(p.name)}
240
+ create_constructor dex_maker, interface_or_class_id, class_type_id, *parameter_type_array
241
+ end
242
+ end
243
+
244
+ #
245
+ # Build a list of methods
246
+ #
247
+
248
+ methods = []
249
+ if interface
250
+ methods = interface_or_class.java_class.declared_instance_methods
251
+ else
252
+ method_hash, klass = {}, interface_or_class
253
+ while klass != nil
254
+ klass.java_class.declared_instance_methods.each do |i|
255
+ if (i.name[0..1] == "on" or (i.modifiers & java.lang.reflect.Modifier::ABSTRACT) != 0) and
256
+ (i.modifiers & java.lang.reflect.Modifier::FINAL) == 0 and not method_hash[i.name]
257
+ method_hash[i.name] = i
258
+ methods << i
259
+ end
260
+ end
261
+ klass = klass == java.lang.Object ? nil : klass.superclass
262
+ end
263
+ end
264
+
265
+ #
266
+ # Build setCallbackProc method
267
+ #
268
+
269
+ method_id = class_type_id.getMethod(TypeId::VOID, "setCallbackProc", TypeId::INT, TypeId::OBJECT)
270
+ dex_maker.declare(method_id, java.lang.reflect.Modifier::PUBLIC).instance_eval do
271
+ index = getParameter(0, TypeId::INT)
272
+ block = getParameter(1, TypeId::OBJECT)
273
+ array = newLocal(TypeId.get("[Ljava/lang/Object;"))
274
+ size = newLocal(TypeId::INT)
275
+ null = newLocal(TypeId::OBJECT)
276
+
277
+ array_exists = com.google.dexmaker.Label.new
278
+
279
+ # Does the calback proc array exist yet?
280
+ iget(callbackProcs_field, array, getThis(class_type_id))
281
+ loadConstant(null, nil)
282
+ compare(com.google.dexmaker.Comparison::NE, array_exists, array, null)
283
+
284
+ # Create the array the first time
285
+ loadConstant(size, methods.length)
286
+ newArray(array, size)
287
+ iput(callbackProcs_field, getThis(class_type_id), array)
288
+
289
+ mark(array_exists)
290
+ aput(array, index, block)
291
+
292
+ returnVoid
293
+ end
294
+
295
+ #
296
+ # Loop through and build a constant and method for each method in the interface
297
+ #
298
+
299
+ methods.each_with_index do |m, count|
300
+ # Define the constant
301
+ constant_name = "CB_" + m.name.gsub(/[A-Z]/, '_\0').upcase.gsub(/^ON_/, "")
302
+ const = class_type_id.getField(TypeId::INT, constant_name)
303
+ dex_maker.declare(const,
304
+ java.lang.reflect.Modifier::PUBLIC | java.lang.reflect.Modifier::STATIC | java.lang.reflect.Modifier::FINAL,
305
+ count.to_java(:int))
306
+
307
+ # Build the method
308
+ parameter_type_array = m.parameter_types.map{|j| TypeId.convert_type(j.name)}
309
+ method_id = class_type_id.getMethod(TypeId.convert_type(m.return_type ? m.return_type.name : nil), m.name, *parameter_type_array)
310
+ dex_maker.declare(method_id, java.lang.reflect.Modifier::PUBLIC).instance_eval do
311
+ parameter_array = []
312
+ parameter_type_array.each_with_index{|j, k| parameter_array << getParameter(k, j)}
313
+
314
+ # Callback procs array
315
+ index = newLocal(TypeId::INT)
316
+ array = newLocal(TypeId.get("[Ljava/lang/Object;"))
317
+ block = newLocal(TypeId::OBJECT)
318
+
319
+ if parameter_array.length > 1
320
+ # Call arguments array
321
+ p_arr = newLocal(TypeId.get("[Ljava/lang/Object;"))
322
+ p_size = newLocal(TypeId::INT)
323
+ p_index = newLocal(TypeId::INT)
324
+ end
325
+
326
+ # Holds nil for comparison
327
+ null = newLocal(TypeId::OBJECT)
328
+
329
+ # Holds method name
330
+ call_string = newLocal(TypeId::STRING)
331
+
332
+ # Locals for possible return
333
+ ret = retObject = nil
334
+ if m.return_type
335
+ ret = newLocal(TypeId.get(m.return_type))
336
+ retObject = newLocal(TypeId::OBJECT)
337
+ retClass = newLocal(TypeId.convert_type("java.lang.Class"))
338
+ end
339
+
340
+ # Create a local to help convert primitives
341
+ tmp_locals = {}
342
+ parameter_type_array.each do |p|
343
+ tmp_locals[p] = newLocal(p.corresponding_class) if p.primitive?
344
+ end
345
+ tmp_locals[ret.type] = newLocal(ret.type.corresponding_class) if ret and ret.type.primitive? and tmp_locals[ret.type] == nil
346
+
347
+ no_block = com.google.dexmaker.Label.new
348
+ done = com.google.dexmaker.Label.new
349
+
350
+ # Does the calback proc array exist yet?
351
+ iget(callbackProcs_field, array, getThis(class_type_id))
352
+ loadConstant(null, nil)
353
+ compare(com.google.dexmaker.Comparison::EQ, no_block, array, null)
354
+
355
+ # Do we have a callback proc?
356
+ loadConstant(index, count)
357
+ aget(block, array, index)
358
+ compare(com.google.dexmaker.Comparison::EQ, no_block, block, null)
359
+
360
+ call_super(class_type_id, m.name, ret, *parameter_array) unless interface
361
+
362
+ # Prepare to call Script to call the method
363
+ script_class_type_id = TypeId.convert_type("org.ruboto.Script")
364
+ loadConstant(call_string, "call")
365
+ parameter_types = [ret ? TypeId::OBJECT : TypeId::VOID, "callMethod", TypeId::OBJECT, TypeId::STRING]
366
+ method_parameters = [retObject, block, call_string]
367
+
368
+ # Set up for different arity
369
+ if parameter_array.length == 1
370
+ parameter_types << TypeId::OBJECT
371
+ # Cast ?
372
+ p = parameter_array[0]
373
+ # Need to convert primitives to add to array
374
+ if p.type.primitive?
375
+ newInstance(tmp_locals[p.type], p.type.corresponding_class.getConstructor(p.type), p)
376
+ method_parameters << tmp_locals[p.type]
377
+ else
378
+ method_parameters << p
379
+ end
380
+ elsif parameter_array.length > 1
381
+ # Create and populate an array for method parameters
382
+ loadConstant(p_size, parameter_type_array.length.to_java(:int))
383
+ newArray(p_arr, p_size)
384
+ parameter_array.each_with_index do |p, i|
385
+ loadConstant(p_index, i)
386
+
387
+ # Need to convert primitives to add to array
388
+ if p.type.primitive?
389
+ newInstance(tmp_locals[p.type], p.type.corresponding_class.getConstructor(p.type), p)
390
+ aput(p_arr, p_index, tmp_locals[p.type])
391
+ else
392
+ aput(p_arr, p_index, p)
393
+ end
394
+ end
395
+
396
+ parameter_types << TypeId.get("[Ljava/lang/Object;")
397
+ method_parameters << p_arr
398
+ end
399
+
400
+ # Add return class to the call
401
+ if ret
402
+ parameter_types << TypeId.convert_type("java.lang.Class")
403
+ loadConstant(retClass, java.lang.Boolean.java_class)
404
+ method_parameters << retClass
405
+ end
406
+
407
+ # Make the call
408
+ method_parameters = [script_class_type_id.getMethod(*parameter_types)] + method_parameters
409
+ invokeStatic(*method_parameters)
410
+
411
+ # Cast the return
412
+ if ret and ret.type.primitive?
413
+ cast(tmp_locals[ret.type], retObject)
414
+ invokeVirtual(tmp_locals[ret.type].type.getMethod(ret.type, ret.type.conversion_method), ret, tmp_locals[ret.type])
415
+ elsif ret
416
+ # May need to just copy if type is OBJECT
417
+ cast(ret, retObject)
418
+ end
419
+
420
+ jump(done)
421
+ mark(no_block)
422
+
423
+ call_super(class_type_id, m.name, ret, *parameter_array) unless interface
424
+
425
+ mark(done)
426
+ ret ? returnValue(ret) : returnVoid
427
+ end
428
+ end
429
+ end
430
+
431
+ ######################################################
432
+ #
433
+ # Clear all generated dex jars
434
+ #
435
+
436
+ def ruboto_clear_dex_cache
437
+ FileUtils.remove_dir "#{$activity.files_dir.absolute_path}/dx"
438
+ end
439
+
440
+ ######################################################
441
+ #
442
+ # Generate classes and configure any widgets
443
+ #
444
+
445
+ def ruboto_generate_widget(options)
446
+ ruboto_generate_widgets(options)
447
+ end
448
+
449
+ def ruboto_generate_widgets(options)
450
+ rv = ruboto_generate(options)
451
+ if rv.is_a?(Array)
452
+ rv.each{|i| ruboto_import_widget i if i < android.view.View}
453
+ else
454
+ ruboto_import_widget rv
455
+ end
456
+ rv
457
+ end
458
+