ruboto-core 0.0.2 → 0.0.3

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,28 @@
1
+ package THE_PACKAGE;
2
+
3
+ import org.jruby.Ruby;
4
+ import org.jruby.javasupport.util.RuntimeHelpers;
5
+ import org.jruby.runtime.builtin.IRubyObject;
6
+ import org.jruby.javasupport.JavaUtil;
7
+ import org.jruby.exceptions.RaiseException;
8
+ import org.ruboto.Script;
9
+
10
+ public class THE_RUBOTO_CLASS THE_ACTION THE_ANDROID_CLASS {
11
+ private Ruby __ruby__;
12
+
13
+ THE_CONSTANTS
14
+ private IRubyObject[] callbackProcs = new IRubyObject[CONSTANTS_COUNT];
15
+
16
+ THE_CONSTRUCTORS
17
+
18
+ private Ruby getRuby() {
19
+ if (__ruby__ == null) __ruby__ = Script.getRuby();
20
+ return __ruby__;
21
+ }
22
+
23
+ public void setCallbackProc(int id, IRubyObject obj) {
24
+ callbackProcs[id] = obj;
25
+ }
26
+
27
+ THE_METHODS
28
+ }
@@ -0,0 +1,122 @@
1
+ package THE_PACKAGE;
2
+
3
+ import org.jruby.Ruby;
4
+ import org.jruby.javasupport.util.RuntimeHelpers;
5
+ import org.jruby.runtime.builtin.IRubyObject;
6
+ import org.jruby.javasupport.JavaUtil;
7
+ import org.jruby.exceptions.RaiseException;
8
+ import org.ruboto.Script;
9
+ import java.io.IOException;
10
+ import android.app.ProgressDialog;
11
+ import android.os.Handler;
12
+
13
+ public class THE_RUBOTO_CLASS THE_ACTION THE_ANDROID_CLASS {
14
+ private Ruby __ruby__;
15
+ private String scriptName;
16
+ private String remoteVariable = "";
17
+ public Object[] args;
18
+ private ProgressDialog loadingDialog;
19
+
20
+ THE_CONSTANTS
21
+ private IRubyObject[] callbackProcs = new IRubyObject[CONSTANTS_COUNT];
22
+
23
+ private Ruby getRuby() {
24
+ if (__ruby__ == null) __ruby__ = Script.getRuby();
25
+
26
+ if (__ruby__ == null) {
27
+ Script.setUpJRuby(null);
28
+ __ruby__ = Script.getRuby();
29
+ }
30
+
31
+ return __ruby__;
32
+ }
33
+
34
+ public void setCallbackProc(int id, IRubyObject obj) {
35
+ callbackProcs[id] = obj;
36
+ }
37
+
38
+ public THE_RUBOTO_CLASS setRemoteVariable(String var) {
39
+ remoteVariable = ((var == null) ? "" : (var + "."));
40
+ return this;
41
+ }
42
+
43
+ public void setScriptName(String name){
44
+ scriptName = name;
45
+ }
46
+
47
+ /****************************************************************************************
48
+ *
49
+ * Activity Lifecycle: onCreate
50
+ */
51
+
52
+ @Override
53
+ public void onCreate(android.os.Bundle arg0) {
54
+ args = new Object[1];
55
+ args[0] = arg0;
56
+
57
+ super.onCreate(arg0);
58
+
59
+ if (Script.getRuby() != null) {
60
+ finishCreate();
61
+ } else {
62
+ loadingThread.start();
63
+ loadingDialog = ProgressDialog.show(this, null, "Loading...", true, false);
64
+ }
65
+ }
66
+
67
+ private final Handler loadingHandler = new Handler();
68
+
69
+ private final Thread loadingThread = new Thread() {
70
+ public void run(){
71
+ Script.setUpJRuby(null);
72
+ loadingHandler.post(loadingComplete);
73
+ }
74
+ };
75
+
76
+ private final Runnable loadingComplete = new Runnable(){
77
+ public void run(){
78
+ loadingDialog.dismiss();
79
+ finishCreate();
80
+ onStart();
81
+ onResume();
82
+ }
83
+ };
84
+
85
+ private void finishCreate() {
86
+ Script.copyScriptsIfNeeded(getFilesDir().getAbsolutePath() + "/scripts", getAssets());
87
+
88
+ getRuby();
89
+
90
+ Script.defineGlobalVariable("$activity", this);
91
+ Script.defineGlobalVariable("$bundle", args[0]);
92
+
93
+ android.os.Bundle configBundle = getIntent().getBundleExtra("RubotoActivity Config");
94
+
95
+ if (configBundle != null) {
96
+ setRemoteVariable(configBundle.getString("Remote Variable"));
97
+ if (configBundle.getBoolean("Define Remote Variable")) {
98
+ Script.defineGlobalVariable(configBundle.getString("Remote Variable"), this);
99
+ setRemoteVariable(configBundle.getString("Remote Variable"));
100
+ }
101
+ if (configBundle.getString("Initialize Script") != null) {
102
+ Script.execute(configBundle.getString("Initialize Script"));
103
+ }
104
+ Script.execute(remoteVariable + "on_create($bundle)");
105
+ } else {
106
+ try {
107
+ new Script(scriptName).execute();
108
+ } catch(IOException e){
109
+ e.printStackTrace();
110
+ ProgressDialog.show(this, "Script failed", "Something bad happened", true, true);
111
+ }
112
+ }
113
+ }
114
+
115
+ /****************************************************************************************
116
+ *
117
+ * Generated Methods
118
+ */
119
+
120
+ THE_METHODS
121
+ }
122
+
@@ -0,0 +1,77 @@
1
+ package THE_PACKAGE;
2
+
3
+ import org.jruby.Ruby;
4
+ import org.jruby.javasupport.util.RuntimeHelpers;
5
+ import org.jruby.runtime.builtin.IRubyObject;
6
+ import org.jruby.javasupport.JavaUtil;
7
+ import org.jruby.exceptions.RaiseException;
8
+ import org.ruboto.Script;
9
+ import java.io.IOException;
10
+ import android.app.ProgressDialog;
11
+
12
+ public abstract class THE_RUBOTO_CLASS THE_ACTION THE_ANDROID_CLASS {
13
+ private Ruby __ruby__;
14
+ private String scriptName;
15
+ private String remoteVariable = "";
16
+ public Object[] args;
17
+
18
+ THE_CONSTANTS
19
+ private IRubyObject[] callbackProcs = new IRubyObject[CONSTANTS_COUNT];
20
+
21
+ private Ruby getRuby() {
22
+ if (__ruby__ == null) __ruby__ = Script.getRuby();
23
+
24
+ if (__ruby__ == null) {
25
+ Script.setUpJRuby(null);
26
+ __ruby__ = Script.getRuby();
27
+ }
28
+
29
+ return __ruby__;
30
+ }
31
+
32
+ public void setCallbackProc(int id, IRubyObject obj) {
33
+ callbackProcs[id] = obj;
34
+ }
35
+
36
+ public THE_RUBOTO_CLASS setRemoteVariable(String var) {
37
+ remoteVariable = ((var == null) ? "" : (var + "."));
38
+ return this;
39
+ }
40
+
41
+ public void setScriptName(String name){
42
+ scriptName = name;
43
+ }
44
+
45
+ /****************************************************************************************
46
+ *
47
+ * Activity Lifecycle: onCreate
48
+ */
49
+
50
+ @Override
51
+ public void onReceive(android.content.Context arg0, android.content.Intent arg1) {
52
+ args = new Object[2];
53
+ args[0] = arg0;
54
+ args[1] = arg1;
55
+
56
+ getRuby();
57
+
58
+ Script.defineGlobalVariable("$broadcast_receiver", this);
59
+ Script.defineGlobalVariable("$broadcast_context", arg0);
60
+ Script.defineGlobalVariable("$broadcast_intent", arg1);
61
+
62
+ try {
63
+ new Script(scriptName).execute();
64
+ } catch(IOException e) {
65
+ e.printStackTrace();
66
+ }
67
+ }
68
+
69
+ /****************************************************************************************
70
+ *
71
+ * Generated Methods
72
+ */
73
+
74
+ THE_METHODS
75
+ }
76
+
77
+
@@ -0,0 +1,75 @@
1
+ package THE_PACKAGE;
2
+
3
+ import org.jruby.Ruby;
4
+ import org.jruby.javasupport.util.RuntimeHelpers;
5
+ import org.jruby.runtime.builtin.IRubyObject;
6
+ import org.jruby.javasupport.JavaUtil;
7
+ import org.jruby.exceptions.RaiseException;
8
+ import org.ruboto.Script;
9
+ import java.io.IOException;
10
+ import android.app.ProgressDialog;
11
+
12
+ public abstract class THE_RUBOTO_CLASS THE_ACTION THE_ANDROID_CLASS {
13
+ private Ruby __ruby__;
14
+ private String scriptName;
15
+ private String remoteVariable = "";
16
+ public Object[] args;
17
+
18
+ THE_CONSTANTS
19
+ private IRubyObject[] callbackProcs = new IRubyObject[CONSTANTS_COUNT];
20
+
21
+ private Ruby getRuby() {
22
+ if (__ruby__ == null) __ruby__ = Script.getRuby();
23
+
24
+ if (__ruby__ == null) {
25
+ Script.setUpJRuby(null);
26
+ __ruby__ = Script.getRuby();
27
+ }
28
+
29
+ return __ruby__;
30
+ }
31
+
32
+ public void setCallbackProc(int id, IRubyObject obj) {
33
+ callbackProcs[id] = obj;
34
+ }
35
+
36
+ public THE_RUBOTO_CLASS setRemoteVariable(String var) {
37
+ remoteVariable = ((var == null) ? "" : (var + "."));
38
+ return this;
39
+ }
40
+
41
+ public void setScriptName(String name){
42
+ scriptName = name;
43
+ }
44
+
45
+ /****************************************************************************************
46
+ *
47
+ * Activity Lifecycle: onCreate
48
+ */
49
+
50
+ @Override
51
+ public void onCreate() {
52
+ args = new Object[0];
53
+
54
+ super.onCreate();
55
+
56
+ getRuby();
57
+
58
+ Script.defineGlobalVariable("$service", this);
59
+
60
+ try {
61
+ new Script(scriptName).execute();
62
+ } catch(IOException e) {
63
+ e.printStackTrace();
64
+ }
65
+ }
66
+
67
+ /****************************************************************************************
68
+ *
69
+ * Generated Methods
70
+ */
71
+
72
+ THE_METHODS
73
+ }
74
+
75
+
data/bin/ruboto CHANGED
@@ -26,6 +26,7 @@ module Main
26
26
  end
27
27
  end
28
28
 
29
+ $gem_root = File.expand_path(__FILE__+ "/../..")
29
30
  $assets = File.expand_path(__FILE__ + "/../../assets")
30
31
 
31
32
  class AssetCopier
@@ -45,6 +46,564 @@ class AssetCopier
45
46
  end
46
47
  end
47
48
 
49
+ ###########################################################################
50
+ #
51
+ # log_action: put text to stdout around the execution of a block
52
+ #
53
+
54
+ def log_action(initial_text, final_text="Done.", &block)
55
+ $stdout.sync = true
56
+ print initial_text, "..."
57
+ yield
58
+ puts final_text
59
+ end
60
+
61
+ ###########################################################################
62
+ #
63
+ # XMLElement:
64
+ # Extends Hash to simulate a REXML::Element (but much faster) and provides
65
+ # information in the necessary format to generate Java code.
66
+ #
67
+
68
+ class XMLElement < Hash
69
+ def root
70
+ $api
71
+ end
72
+
73
+ def name
74
+ self["name"]
75
+ end
76
+
77
+ def attribute(name)
78
+ self["values"][name]
79
+ end
80
+
81
+ def add_element(name, attributes)
82
+ new_element = XMLElement.new
83
+ new_element["name"] = name
84
+ new_element["values"] = attributes
85
+
86
+ self[name] = [] unless self[name]
87
+ self[name] << new_element
88
+
89
+ new_element
90
+ end
91
+
92
+ def get_elements(name)
93
+ self[name] or []
94
+ end
95
+
96
+ def find_class_or_interface(klass, a_type)
97
+ abort "ERROR: Can't parse package from #{klass}" unless klass.match(/([a-z.]+)\.([A-Z][A-Za-z.]+)/)
98
+ package = self["package"].find{|i| i.attribute("name") == $1}
99
+ abort "ERROR: Can't find package #{$1}" unless package
100
+ if a_type == "either"
101
+ package["class"].find{|i| i.attribute("name") == $2} or package["interface"].find{|i| i.attribute("name") == $2}
102
+ else
103
+ package[a_type].find{|i| i.attribute("name") == $2}
104
+ end
105
+ end
106
+
107
+ def find_class(package_and_class)
108
+ find_class_or_interface(package_and_class, "class")
109
+ end
110
+
111
+ def find_interface(package_and_interface)
112
+ find_class_or_interface(package_and_interface, "interface")
113
+ end
114
+
115
+ def all_methods(method_base="all", method_include="", method_exclude="", implements="")
116
+ # get all the methogs
117
+ all_methods = get_elements("method")
118
+
119
+ # establish the base set of methods
120
+ working_methods = case method_base
121
+ when "all"
122
+ all_methods
123
+ when "none"
124
+ []
125
+ when "abstract"
126
+ all_methods.select{|i| i.attribute("abstract") == "true"}
127
+ when "on"
128
+ all_methods.select{|i| i.attribute("name").match(/^on[A-Z]/)}
129
+ end
130
+
131
+ # make sure to include requested methods
132
+ include_methods = method_include.split(",") if method_include.is_a?(String)
133
+ all_methods.each{|i| working_methods << i if include_methods.include?(i.attribute("name"))}
134
+
135
+ # make sure to exclude rejected methods
136
+ exclude_methods = method_exclude.split(",") if method_exclude.is_a?(String)
137
+ working_methods = working_methods.select{|i| not exclude_methods.include?(i.attribute("name"))}
138
+
139
+ # remove methods marked final
140
+ working_methods = working_methods.select{|i| (not i.attribute("final")) or i.attribute("final") == "false"}
141
+
142
+ # get additional methods from parent
143
+ if name =="class" and attribute("extends")
144
+ parent = root.find_class(attribute("extends"))
145
+ parent_methods = parent.all_methods(method_base, method_include, method_exclude)
146
+ working_signatures = working_methods.map(&:method_signature)
147
+ working_methods += parent_methods.select{|i| not working_signatures.include?(i.method_signature)}
148
+ end
149
+
150
+ # get additional methods from interfaces
151
+ if name =="class" and implements != ""
152
+ implements.split(",").each do |i|
153
+ interface = root.find_interface(i)
154
+ abort("Unkown interface: #{i}") unless interface
155
+ working_signatures = working_methods.map(&:method_signature)
156
+ working_methods += interface.all_methods.select{|j| not working_signatures.include?(j.method_signature)}
157
+ end
158
+ end
159
+
160
+ working_methods
161
+ end
162
+
163
+ def parameters
164
+ get_elements("parameter").map {|p| [p.attribute("name"), p.attribute("type").gsub("&lt;", "<").gsub("&gt;", ">")]}
165
+ end
166
+
167
+ def method_signature
168
+ "#{attribute("name")}(#{parameters.map{|i| i[1]}.join(',')})"
169
+ end
170
+
171
+ def constant_string
172
+ "CB_" + attribute("name").gsub(/[A-Z]/) {|i| "_#{i}"}.upcase.gsub(/^ON_/, "")
173
+ end
174
+
175
+ def super_string
176
+ if attribute("api_added") and
177
+ attribute("api_added").to_i > verify_min_sdk.to_i and
178
+ attribute("api_added").to_i <= verify_target_sdk.to_i
179
+ nil
180
+ elsif attribute("abstract") == "true"
181
+ nil
182
+ elsif name == "method"
183
+ "super.#{attribute("name")}(#{parameters.map{|i| i[0]}.join(", ")});"
184
+ elsif name == "constructor"
185
+ "super(#{parameters.map{|i| i[0]}.join(", ")});"
186
+ end
187
+ end
188
+
189
+ def default_return
190
+ return nil unless attribute("return")
191
+ case attribute("return")
192
+ when "boolean": "return false;"
193
+ when "int": "return 0;"
194
+ when "void": nil
195
+ else "return null;"
196
+ end
197
+ end
198
+
199
+ def super_return
200
+ rv = super_string
201
+ return rv unless attribute("return")
202
+ rv ? "return #{rv}" : default_return
203
+ end
204
+
205
+ def ruby_call
206
+ rv = []
207
+
208
+ params = parameters
209
+ args = ""
210
+ if params.size > 3
211
+ args = ", args"
212
+ rv << "IRubyObject[] args = {" + params.map{|i| "JavaUtil.convertJavaToRuby(getRuby(), #{i[0]})"}.join(", ") + "};"
213
+ elsif params.size > 0
214
+ args = ", " + params.map{|i| "JavaUtil.convertJavaToRuby(getRuby(), #{i[0]})"}.join(", ")
215
+ end
216
+
217
+ return_cast = ""
218
+ if attribute("return") and (attribute("return").include?(".") or attribute("return") == "int[]")
219
+ return_cast = "return (#{attribute("return")})"
220
+ elsif attribute("return") and attribute("return") == "int"
221
+ return_cast = "return (Integer)"
222
+ elsif attribute("return") and attribute("return") != "void"
223
+ return_cast = "return (#{attribute("return").capitalize})"
224
+ end
225
+ return_cast = return_cast.gsub("&lt;", "<").gsub("&gt;", ">")
226
+
227
+ convert_return = ""
228
+ if attribute("return") and attribute("return") != "void"
229
+ convert_return = ".toJava(#{attribute("return")}.class)"
230
+ end
231
+
232
+ rv << "#{return_cast}RuntimeHelpers.invoke(getRuby().getCurrentContext(), callbackProcs[#{constant_string}], \"call\" #{args})#{convert_return};"
233
+ rv
234
+ end
235
+
236
+ def method_definition
237
+ method_call((attribute("return") ? attribute("return") : "void"), attribute("name"), parameters,
238
+ if_else("callbackProcs[#{constant_string}] != null",
239
+ [super_string] + try_catch(ruby_call, ["re.printStackTrace();", default_return]),
240
+ [super_return])).indent.join("\n")
241
+ end
242
+
243
+ def constructor_definition(class_name)
244
+ method_call("", class_name, parameters, [super_string]).indent.join("\n")
245
+ end
246
+ end
247
+
248
+ ###########################################################################
249
+ #
250
+ # Methods for formatting code
251
+ #
252
+
253
+ def method_call(return_type=nil, method_name="", parameters=[], body_clause=[])
254
+ ["public #{return_type || ""} #{method_name}(" + parameters.map{|i| "#{i[1]} #{i[0]}"}.join(", ") + ") {",
255
+ body_clause.indent, "}"]
256
+ end
257
+
258
+ def if_else(condition, if_clause, else_clause)
259
+ ["if (#{condition}) {", if_clause.indent, else_clause.compact.empty? ? nil : "} else {", else_clause.indent, "}"]
260
+ end
261
+
262
+ def try_catch(try_clause, catch_clause)
263
+ ["try {", try_clause.indent, "} catch (RaiseException re) {", catch_clause.indent, "}"]
264
+ end
265
+
266
+ class Array
267
+ def indent
268
+ flatten.compact.map{|i| " " + i}
269
+ end
270
+ end
271
+
272
+ ###########################################################################
273
+ #
274
+ # Build Subclass or Interface:
275
+ #
276
+
277
+ #
278
+ # build_file: Reads the src from the appropriate location,
279
+ # uses the substitutions hash to modify the contents,
280
+ # and writes to the new location
281
+ #
282
+ def build_file(src, package, name, substitutions, dest='.')
283
+ to = File.join(dest, "src/#{package.gsub('.', '/')}")
284
+ Dir.mkdir(to) unless File.directory?(to)
285
+
286
+ text = File.read(File.expand_path($gem_root + "/assets/src/#{src}.java"))
287
+ substitutions.each {|k,v| text.gsub!(k, v)}
288
+
289
+ File.open(File.join(to, "#{name}.java"), 'w') {|f| f << text}
290
+ end
291
+
292
+ #
293
+ # get_class_or_interface: Opens the xml file and locates the specified class.
294
+ # Aborts if the class is not found or if it is not available for
295
+ # all api levels
296
+ #
297
+ def get_class_or_interface(klass, force=false)
298
+ element = verify_api.find_class_or_interface(klass, "either")
299
+
300
+ abort "ERROR: #{klass} not found" unless element
301
+
302
+ unless force
303
+ abort "#{klass} not available in minSdkVersion, added in #{element.attribute('api_added')}; use --force to create it" if
304
+ element.attribute('api_added') and element.attribute('api_added').to_i > verify_min_sdk.to_i
305
+ abort "#{klass} deprecated for targetSdkVersion, deprecatrd in #{element.attribute('deprecated')}; use --force to create it" if
306
+ element.attribute('deprecated') and element.attribute('deprecated').to_i <= verify_target_sdk.to_i
307
+ end
308
+
309
+ abort "#{klass} removed for targetSdkVersion, removed in #{element.attribute('api_removed')}" if
310
+ element.attribute('api_removed') and element.attribute('api_removed').to_i <= verify_target_sdk.to_i
311
+
312
+ element
313
+ end
314
+
315
+ #
316
+ # check_methods: Checks the methods to see if they are available for all api levels
317
+ #
318
+ def check_methods(methods, force=false)
319
+ min_api = verify_min_sdk.to_i
320
+ target_api = verify_target_sdk.to_i
321
+
322
+ # Remove methods changed outside of the scope of the sdk versions
323
+ methods = methods.select{|i| not i.attribute('api_added') or i.attribute('api_added').to_i <= target_api}
324
+ methods = methods.select{|i| not i.attribute('deprecated') or i.attribute('deprecated').to_i > min_api}
325
+ methods = methods.select{|i| not i.attribute('api_removed') or i.attribute('api_removed').to_i > min_api}
326
+
327
+ # Inform and remove methods that do not exist in one of the sdk versions
328
+ methods = methods.select do |i|
329
+ if i.attribute('api_removed') and i.attribute('api_removed').to_i <= target_api
330
+ puts "Can't create #{i.method_signature} -- removed in #{i.attribute('api_removed')}"
331
+ false
332
+ else
333
+ true
334
+ end
335
+ end
336
+
337
+ new_methods = methods
338
+ unless force
339
+ # Inform and remove methods changed inside the scope of the sdk versions
340
+ new_methods = methods.select do |i|
341
+ if i.attribute('api_added') and i.attribute('api_added').to_i > min_api
342
+ puts "Can't create #{i.method_signature} -- added in #{i.attribute('api_added')} -- exclude or force"
343
+ false
344
+ elsif i.attribute('deprecated') and i.attribute('deprecated').to_i <= target_api
345
+ puts "Can't create #{i.method_signature} -- deprecated in #{i.attribute('deprecated')} -- exclude or force"
346
+ false
347
+ else
348
+ true
349
+ end
350
+ end
351
+
352
+ abort("Aborting!") if methods.count != new_methods.count
353
+ end
354
+
355
+ new_methods
356
+ end
357
+
358
+ #
359
+ # generate_subclass_or_interface: Creates a subclass or interface based on the specifications.
360
+ #
361
+ def generate_subclass_or_interface(params)
362
+ defaults = {:template => "InheritingClass", :method_base => "all", :method_include => "", :method_exclude => "", :force => false, :implements => ""}
363
+ params = defaults.merge(params)
364
+ params[:package] = verify_package unless params[:package]
365
+
366
+ class_desc = get_class_or_interface(params[:class] || params[:interface], params[:force])
367
+
368
+ puts "Generating methods for #{params[:name]}..."
369
+ methods = class_desc.all_methods(params[:method_base], params[:method_include], params[:method_exclude], params[:implements])
370
+ methods = check_methods(methods, params[:force])
371
+ puts "Done. Methods created: #{methods.count}"
372
+
373
+ # Remove any duplicate constants (use *args handle multiple parameter lists)
374
+ constants = methods.map(&:constant_string).uniq
375
+
376
+ build_file params[:template], params[:package], params[:name], {
377
+ "THE_PACKAGE" => params[:package],
378
+ "THE_ACTION" => class_desc.name == "class" ? "extends" : "implements",
379
+ "THE_ANDROID_CLASS" => (params[:class] || params[:interface]) +
380
+ (params[:implements] == "" ? "" : (" implements " + params[:implements].split(",").join(", "))),
381
+ "THE_RUBOTO_CLASS" => params[:name],
382
+ "THE_CONSTANTS" => constants.map {|i| "public static final int #{i} = #{constants.index(i)};"}.indent.join("\n"),
383
+ "CONSTANTS_COUNT" => methods.count.to_s,
384
+ "THE_CONSTRUCTORS" => class_desc.name == "class" ?
385
+ class_desc.get_elements("constructor").map{|i| i.constructor_definition(params[:name])}.join("\n\n") : "",
386
+ "THE_METHODS" => methods.map{|i| i.method_definition}.join("\n\n")
387
+ }
388
+ end
389
+
390
+ #
391
+ # generate_core_classe: generates RubotoActivity, RubotoService, etc. based
392
+ # on the API specifications.
393
+ #
394
+ def generate_core_classes(params)
395
+ %w(android.view.View.OnClickListener android.widget.AdapterView.OnItemClickListener).each do |i|
396
+ name = i.split(".")[-1]
397
+ if(params[:class] == name or params[:class] == "all")
398
+ generate_subclass_or_interface({:package => "org.ruboto.callbacks", :class => i, :name => "Ruboto#{name}"})
399
+ end
400
+ end
401
+
402
+ hash = {:package => "org.ruboto"}
403
+ %w(method_base method_include implements force).inject(hash) {|h, i| h[i.to_sym] = params[i.to_sym]; h}
404
+ hash[:method_exclude] = params[:method_exclude].split(",").push("onCreate").push("onReceive").join(",")
405
+
406
+ %w(android.app.Activity android.app.Service android.content.BroadcastReceiver android.view.View).each do |i|
407
+ name = i.split(".")[-1]
408
+ if(params[:class] == name or params[:class] == "all")
409
+ generate_subclass_or_interface(
410
+ hash.merge({:template => name == "View" ? "InheritingClass" : "Ruboto#{name}", :class => i, :name => "Ruboto#{name}"}))
411
+ end
412
+ end
413
+
414
+ # Activities that can be created, but only directly (i.e., not included in all)
415
+ %w(android.preference.PreferenceActivity android.app.TabActivity).each do |i|
416
+ name = i.split(".")[-1]
417
+ if params[:class] == name
418
+ generate_subclass_or_interface(hash.merge({:template => "RubotoActivity", :class => i, :name => "Ruboto#{name}"}))
419
+ end
420
+ end
421
+ end
422
+
423
+ ###########################################################################
424
+ #
425
+ # Updating components
426
+ #
427
+
428
+ def update_jruby(force=nil)
429
+ jruby_core = Dir.glob("libs/jruby-core-*.jar")[0]
430
+ jruby_stdlib = Dir.glob("libs/jruby-stdlib-*.jar")[0]
431
+ new_jruby_version = JRubyJars::core_jar_path.split('/')[-1][11..-5]
432
+
433
+ unless force
434
+ abort "cannot find existing jruby jars in libs. Make sure you're in the root directory of your app" if
435
+ (not jruby_core or not jruby_stdlib)
436
+
437
+ current_jruby_version = jruby_core ? jruby_core[16..-5] : "None"
438
+ abort "both jruby versions are #{new_jruby_version}. Nothing to update. Make sure you 'gem update jruby-jars' if there is a new version" if
439
+ current_jruby_version == new_jruby_version
440
+
441
+ puts "Current jruby version: #{current_jruby_version}"
442
+ puts "New jruby version: #{new_jruby_version}"
443
+ end
444
+
445
+ copier = AssetCopier.new $assets, File.expand_path(".")
446
+ log_action("Removing #{jruby_core}") {File.delete jruby_core} if jruby_core
447
+ log_action("Removing #{jruby_stdlib}") {File.delete jruby_stdlib} if jruby_stdlib
448
+ log_action("Copying #{JRubyJars::core_jar_path} to libs") {copier.copy_from_absolute_path JRubyJars::core_jar_path, "libs"}
449
+ log_action("Copying #{JRubyJars::stdlib_jar_path} to libs") {copier.copy_from_absolute_path JRubyJars::stdlib_jar_path, "libs"}
450
+
451
+ reconfigure_jruby_libs
452
+
453
+ puts "JRuby version is now: #{new_jruby_version}"
454
+ end
455
+
456
+ def update_ruboto(force=nil)
457
+ verify_manifest
458
+
459
+ from = File.expand_path($gem_root + "/assets/assets/scripts/ruboto.rb")
460
+ to = File.expand_path("./assets/scripts/ruboto.rb")
461
+
462
+ from_text = File.read(from)
463
+ to_text = File.read(to) if File.exists?(to)
464
+
465
+ unless force
466
+ puts "New version: #{from_text[/\$RUBOTO_VERSION = (\d+)/, 1]}"
467
+ puts "Old version: #{to_text ? to_text[/\$RUBOTO_VERSION = (\d+)/, 1] : 'none'}"
468
+
469
+ abort "The ruboto.rb verion has not changed. Use --force to force update." if
470
+ from_text[/\$RUBOTO_VERSION = (\d+)/, 1] == to_text[/\$RUBOTO_VERSION = (\d+)/, 1]
471
+ end
472
+
473
+ log_action("Copying ruboto.rb and setting the package name") do
474
+ File.open(to, 'w') {|f| f << from_text.gsub("THE_PACKAGE", verify_package).gsub("ACTIVITY_NAME", verify_activity)}
475
+ end
476
+ end
477
+
478
+ #
479
+ # reconfigure_jruby_libs:
480
+ # - removes unneeded code from jruby-core
481
+ # - moves ruby stdlib to the root of the ruby-stdlib jar
482
+ #
483
+
484
+ def reconfigure_jruby_libs
485
+ jruby_core = JRubyJars::core_jar_path.split('/')[-1]
486
+ log_action("Removing unneeded classes from #{jruby_core}") do
487
+ Dir.mkdir "libs/tmp"
488
+ Dir.chdir "libs/tmp"
489
+ FileUtils.move "../#{jruby_core}", "."
490
+ `jar -xf #{jruby_core}`
491
+ File.delete jruby_core
492
+ ['jni', 'org/jruby/ant', 'org/jruby/compiler/ir', 'org/jruby/demo', 'org/jruby/embed/bsf',
493
+ 'org/jruby/embed/jsr223', 'org/jruby/ext/ffi','org/jruby/javasupport/bsf'
494
+ ].each {|i| FileUtils.remove_dir i, true}
495
+ `jar -cf ../#{jruby_core} .`
496
+ Dir.chdir "../.."
497
+ FileUtils.remove_dir "libs/tmp", true
498
+ end
499
+
500
+ jruby_stdlib = JRubyJars::stdlib_jar_path.split('/')[-1]
501
+ log_action("Reformatting #{jruby_stdlib}") do
502
+ Dir.mkdir "libs/tmp"
503
+ Dir.chdir "libs/tmp"
504
+ FileUtils.move "../#{jruby_stdlib}", "."
505
+ `jar -xf #{jruby_stdlib}`
506
+ File.delete jruby_stdlib
507
+ FileUtils.move "META-INF/jruby.home/lib/ruby/1.8", ".."
508
+ Dir.chdir "../1.8"
509
+ FileUtils.remove_dir "../tmp", true
510
+ `jar -cf ../#{jruby_stdlib} .`
511
+ Dir.chdir "../.."
512
+ FileUtils.remove_dir "libs/1.8", true
513
+ end
514
+ end
515
+
516
+ ###########################################################################
517
+ #
518
+ # Verify the presence of important components
519
+ #
520
+
521
+ def verify_manifest
522
+ abort "cannot find your AndroidManifest.xml to extract info from it. Make sure you're in the root directory of your app" unless
523
+ File.exists? 'AndroidManifest.xml'
524
+ @manifest ||= REXML::Document.new(File.read('AndroidManifest.xml')).root
525
+ end
526
+
527
+ def verify_package
528
+ verify_manifest
529
+ @package ||= @manifest.attribute('package').value
530
+ end
531
+
532
+ def verify_activity
533
+ verify_manifest
534
+ @activity ||= @manifest.elements['application/activity'].attribute('android:name').value
535
+ end
536
+
537
+ def verify_sdk_versions
538
+ verify_manifest
539
+ @uses_sdk ||= @manifest.elements["uses-sdk"]
540
+ abort "you must specify your sdk level in the manifest (e.g., <uses-sdk android:minSdkVersion='3' android:targetSdkVersion='8' />)" unless @uses_sdk
541
+ @uses_sdk
542
+ end
543
+
544
+ def verify_min_sdk
545
+ verify_sdk_versions
546
+ @min_sdk ||= @uses_sdk.attribute('android:minSdkVersion').value
547
+ abort "you must specify a minimum sdk level in the manifest (e.g., <uses-sdk android:minSdkVersion='3' android:targetSdkVersion='8' />)" unless @min_sdk
548
+ @min_sdk
549
+ end
550
+
551
+ def verify_target_sdk
552
+ verify_sdk_versions
553
+ @target_sdk ||= @uses_sdk.attribute('android:targetSdkVersion').value
554
+ abort "you must specify a target sdk level in the manifest (e.g., <uses-sdk android:minSdkVersion='3' android:targetSdkVersion='8' />)" unless @target_sdk
555
+ @target_sdk
556
+ end
557
+
558
+ def verify_api
559
+ unless $api
560
+ api = File.expand_path($gem_root + "/lib/java_class_gen/android_api.xml")
561
+ abort "cannot find android_api.xml to extract info from it." unless File.exists? api
562
+ log_action("Loading Android API") {$api = scan_in_api(File.read(api))["api"][0]}
563
+ end
564
+ $api
565
+ end
566
+
567
+ ###########################################################################
568
+ #
569
+ # Scan the XML file. Much faster than using REXML.
570
+ #
571
+
572
+ def scan_in_api(file)
573
+ require 'strscan'
574
+ doc = StringScanner.new(file)
575
+ $api = XMLElement.new
576
+ parents = [$api]
577
+
578
+ while not doc.eos?
579
+ doc.scan(/</)
580
+ if doc.scan(/\/\w+>/)
581
+ parents.pop
582
+ else
583
+ name = doc.scan(/\w+/)
584
+ doc.scan(/\s+/)
585
+ values = {}
586
+ while not (term = doc.scan(/[\/>]/))
587
+ key = doc.scan(/\w+/)
588
+ doc.scan(/='/)
589
+ value = doc.scan(/[^']*/)
590
+ doc.scan(/'\s*/)
591
+ values[key] = value
592
+ end
593
+ element = parents[-1].add_element(name, values)
594
+ parents.push(element) if term == ">"
595
+ doc.scan(/>/) if term == "/"
596
+ end
597
+ end
598
+ $api
599
+ end
600
+
601
+ ###########################################################################
602
+ #
603
+ # generate_inheriting_file:
604
+ # Builds a script based subclass of Activity, Service, or BroadcastReceiver
605
+ #
606
+
48
607
  def generate_inheriting_file(klass, name, package, script_name, dest='.')
49
608
  to = File.join(dest, "src/#{package.gsub('.', '/')}")
50
609
 
@@ -85,7 +644,14 @@ Main {
85
644
  option("target") {
86
645
  required
87
646
  argument :required
88
- description "android version to target. must begin with 'android-'. Currently must be 'android-8'"
647
+ defaults 'android-9'
648
+ description "android version to target. must begin with 'android-' (e.g., 'android-8' for froyo)"
649
+ }
650
+ option("min_sdk") {
651
+ required
652
+ argument :required
653
+ defaults 'android-7'
654
+ description "minimum android version supported. must begin with 'android-'. (default 'android-3')"
89
655
  }
90
656
  option("path"){
91
657
  required
@@ -95,11 +661,13 @@ Main {
95
661
  option("package"){
96
662
  required
97
663
  argument :required
664
+ defaults 'org.ruboto.example'
98
665
  description "Name of package. Must be unique for every app. A common pattern is yourtld.yourdomain.appname (Ex. org.ruboto.irb)"
99
666
  }
100
667
  option("activity"){
101
668
  required
102
669
  argument :required
670
+ defaults 'Main'
103
671
  description "name of your primary Activity"
104
672
  }
105
673
 
@@ -107,15 +675,13 @@ Main {
107
675
  path = params['path'].value
108
676
  name = params['name'].value
109
677
  target = params['target'].value
678
+ min_sdk = params['min_sdk'].value
110
679
  package = params['package'].value
111
680
  activity = params['activity'].value
112
681
 
113
682
  abort "path must be to a directory that does not yet exist. it will be created" if
114
683
  File.exists?(path)
115
684
 
116
- abort "Currently you must set target to 'android-8' (Froyo) for ruboto to work" unless
117
- target == 'android-8'
118
-
119
685
  root = File.expand_path(path)
120
686
  print "\nGenerating Android app #{name} in #{root}..."
121
687
  `android create project -n #{name} -t #{target} -p #{path} -k #{package} -a #{activity}`
@@ -125,48 +691,35 @@ Main {
125
691
  copier = AssetCopier.new $assets, root
126
692
 
127
693
  %w{Rakefile .gitignore assets}.each do |f|
128
- print "#{f}..."
129
- copier.copy f
130
- puts "Done"
694
+ log_action(f) {copier.copy f}
131
695
  end
132
696
 
133
- print "JRuby jars..."
134
- copier.copy_from_absolute_path JRubyJars::core_jar_path, "libs"
135
- copier.copy_from_absolute_path JRubyJars::stdlib_jar_path, "libs"
136
- puts "Done"
697
+ log_action("Ruboto java classes"){copier.copy "src/org/ruboto/*.java", "src/org/ruboto"}
137
698
 
138
- print "Ruboto java classes..."
139
- copier.copy "src/org/ruboto/*.java", "src/org/ruboto"
140
- puts "Done"
699
+ # Remember the current directory and chdir to the new app directory
700
+ current_dir = Dir.pwd
701
+ Dir.chdir root
141
702
 
142
- print "\nAdding RubotoActivity and RubotoDialog to the manifest..."
143
- file = File.join(root, "AndroidManifest.xml")
144
- text = File.read(file)
145
- File.open(file, 'w') do |f|
146
- f << text.gsub("</application>",
147
- %Q{
148
- <activity android:name="org.ruboto.RubotoActivity">
149
- </activity>
150
- <activity android:name="org.ruboto.RubotoDialog" android:theme="@android:style/Theme.Dialog">
151
- </activity>
152
- </application>
153
- })
703
+ update_jruby true
704
+
705
+ log_action("\nAdding activities (RubotoActivity and RubotoDialog) and SDK versions to the manifest") do
706
+ verify_manifest.elements['application'].add_element 'activity', {"android:name" => "org.ruboto.RubotoActivity"}
707
+ verify_manifest.elements['application'].add_element 'activity', {"android:name" => "org.ruboto.RubotoDialog",
708
+ "android:theme" => "@android:style/Theme.Dialog"}
709
+ verify_manifest.add_element 'uses-sdk', {"android:minSdkVersion" => min_sdk[/\d+/], "android:targetSdkVersion" => target[/\d+/]}
710
+ File.open("AndroidManifest.xml", 'w') {|f| verify_manifest.document.write(f, 4)}
154
711
  end
155
- puts "Done"
156
712
 
157
- print "Setting the package name in ruboto.rb..."
158
- java_files = [File.join(root, "assets/scripts/ruboto.rb")]
159
- java_files.each do |file|
160
- text = File.read(file)
161
- File.open(file, 'w') do |f|
162
- f << text.gsub("THE_PACKAGE", package).gsub("ACTIVITY_NAME", activity)
163
- end
164
- end
165
- puts "Done"
713
+ update_ruboto true
166
714
 
167
- print "Generating the default Activity and script..."
168
- generate_inheriting_file "Activity", activity, package, "#{underscore(activity)}.rb", path
169
- puts "Done"
715
+ generate_core_classes(:class => "all", :method_base => "on", :method_include => "", :method_exclude => "", :force => true, :implements => "")
716
+
717
+ # Go back to whence we came
718
+ Dir.chdir current_dir
719
+
720
+ log_action("Generating the default Activity and script") do
721
+ generate_inheriting_file "Activity", activity, package, "#{underscore(activity)}.rb", path
722
+ end
170
723
 
171
724
  puts "\nHello, #{name}\n"
172
725
  end
@@ -191,15 +744,130 @@ Main {
191
744
 
192
745
 
193
746
  def run
194
- abort "cannot find your AndroidManifest.xml to extract info from it. Make sure you're in the root directory of your app" unless
195
- File.exists? 'AndroidManifest.xml'
196
-
197
- package = REXML::Document.new(File.read('AndroidManifest.xml')).root.attribute('package').value
198
747
  name = params['name'].value
199
748
  script_name = params['script_name'].value || "#{underscore(name)}.rb"
200
749
  klass = params['class'].value
201
750
 
202
- generate_inheriting_file klass, name, package, script_name
751
+ generate_inheriting_file klass, name, verify_package, script_name
752
+ end
753
+ end
754
+
755
+ mode "subclass" do
756
+ argument("class"){
757
+ required
758
+ description "the Android Class that you want to subclass (e.g., package.Class)."
759
+ }
760
+
761
+ option("name"){
762
+ required
763
+ argument :required
764
+ description "name of the class (and file). Should be CamelCase"
765
+ }
766
+
767
+ option("method_base"){
768
+ required
769
+ validate {|i| %w(all on none abstract).include?(i)}
770
+ argument :required
771
+ description "the base set of methods to generate (adjusted with method_include and method_exclude): all, none, abstract, on (e.g., onClick)"
772
+ }
773
+
774
+ option("method_include"){
775
+ argument :required
776
+ defaults ""
777
+ description "additional methods to add to the base list"
778
+ }
779
+
780
+ option("method_exclude"){
781
+ argument :required
782
+ defaults ""
783
+ description "methods to remove from the base list"
784
+ }
785
+
786
+ option("implements"){
787
+ required
788
+ argument :required
789
+ defaults ""
790
+ description "comma separated list interfaces to implement"
791
+ }
792
+
793
+ option("force"){
794
+ cast :boolean
795
+ description "force added and deprecated methods not excluded to be create"
796
+ }
797
+
798
+ def run
799
+ generate_subclass_or_interface(
800
+ %w(class name method_base method_include method_exclude implements force).inject({}) {|h, i| h[i.to_sym] = params[i].value; h})
801
+ end
802
+ end
803
+
804
+ mode "interface" do
805
+ argument("interface"){
806
+ required
807
+ description "the Android Interface that you want to implement (e.g., package.Interface)."
808
+ }
809
+
810
+ option("name"){
811
+ required
812
+ argument :required
813
+ description "name of the class (and file) that will implement the interface. Should be CamelCase"
814
+ }
815
+
816
+ option("force"){
817
+ cast :boolean
818
+ description "force added and deprecated interfaces to be create"
819
+ }
820
+
821
+ def run
822
+ generate_subclass_or_interface %w(interface name force).inject({}) {|h, i| h[i.to_sym] = params[i].value; h}
823
+ end
824
+ end
825
+
826
+ mode "core" do
827
+ argument("class"){
828
+ required
829
+ validate {|i| %w(Activity Service BroadcastReceiver View PreferenceActivity TabActivity OnClickListener OnItemClickListener all).include?(i)}
830
+ description "Activity, Service, BroadcastReceiver, View, OnClickListener, OnItemClickListener, or all (default = all); Other activities not included in 'all': PreferenceActivity, TabActivity"
831
+ }
832
+
833
+ option("method_base"){
834
+ required
835
+ argument :required
836
+ validate {|i| %w(all on none).include?(i)}
837
+ defaults "on"
838
+ description "the base set of methods to generate (adjusted with method_include and method_exclude): all, none, on (e.g., onClick)"
839
+ }
840
+
841
+ option("method_include"){
842
+ required
843
+ argument :required
844
+ defaults ""
845
+ description "additional methods to add to the base list"
846
+ }
847
+
848
+ option("method_exclude"){
849
+ required
850
+ argument :required
851
+ defaults ""
852
+ description "methods to remove from the base list"
853
+ }
854
+
855
+ option("implements"){
856
+ required
857
+ argument :required
858
+ defaults ""
859
+ description "for classes only, interfaces to implement (cannot be used with 'gen core all')"
860
+ }
861
+
862
+ option("force"){
863
+ cast :boolean
864
+ description "force added and deprecated methods not excluded to be create"
865
+ }
866
+
867
+ def run
868
+ abort("specify 'implements' only for Activity, Service, BroadcastReceiver, PreferenceActivity, or TabActivity") unless
869
+ %w(Activity Service BroadcastReceiver PreferenceActivity TabActivity).include?(params["class"].value) or params["implements"].value == ""
870
+ generate_core_classes [:class, :method_base, :method_include, :method_exclude, :implements, :force].inject({}) {|h, i| h[i] = params[i.to_s].value; h}
203
871
  end
204
872
  end
205
873
 
@@ -223,11 +891,31 @@ Main {
223
891
  end
224
892
  end
225
893
 
894
+ mode "update-jruby" do
895
+ argument("what"){
896
+ required
897
+ validate {|i| %w(jruby ruboto).include?(i)}
898
+ description "What do you want to update: 'jruby' or 'ruboto'"
899
+ }
900
+
901
+ option("force"){
902
+ description "force and update even if the version hasn't changed"
903
+ }
904
+
905
+ def run
906
+ case params['what'].value
907
+ when "jruby": update_jruby(params['force'].value);
908
+ when "ruboto": update_ruboto(params['force'].value);
909
+ end
910
+ end
911
+ end
912
+
226
913
  # just running `ruboto`
227
914
  def run
228
915
  puts %Q{
229
916
  Ruboto -- Ruby for Android
230
917
  Execute `ruboto gen app --help` for instructions on how to generate a fresh Ruby-enabled Android app
918
+ Execute `ruboto --help` for other options
231
919
  }
232
920
  end
233
921
  }