ruboto 0.5.4 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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
+