ruboto-core 0.0.2 → 0.0.3

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.
@@ -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
  }