contrib/jwrapper: add an option to not generate methods
[nit.git] / contrib / jwrapper / src / code_generator.nit
1 # This file is part of NIT (http://www.nitlanguage.org).
2 #
3 # Copyright 2014 Frédéric Vachon <fredvac@gmail.com>
4 # Copyright 2015 Alexis Laferrière <alexis.laf@xymus.net>
5 #
6 # Licensed under the Apache License, Version 2.0 (the "License");
7 # you may not use this file except in compliance with the License.
8 # You may obtain a copy of the License at
9 #
10 # http://www.apache.org/licenses/LICENSE-2.0
11 #
12 # Unless required by applicable law or agreed to in writing, software
13 # distributed under the License is distributed on an "AS IS" BASIS,
14 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 # See the License for the specific language governing permissions and
16 # limitations under the License.
17
18 # Services to generate extern class `in "Java"`
19 module code_generator
20
21 intrude import model
22
23 class CodeGenerator
24
25 # Path to the output file
26 var file_name: String
27
28 # Model of Java class being wrapped
29 var model: JavaModel
30
31 # Comment out methods with unknown (unwrapped) types
32 var comment_unknown_types: Bool
33
34 # Generate stub classes for unknown types used in the generated module
35 var stub_for_unknown_types: Bool
36
37 # Output file
38 var file_out: Writer = new FileWriter.open(file_name) is lazy, writable
39
40 # Name of the Nit module to generate
41 var module_name: nullable String is lazy do
42 if file_name.file_extension == "nit" then
43 # Output file ends with .nit, we expect it to be a valid name
44 return file_name.basename(".nit")
45 else return null
46 end
47
48 # Generate the Nit module into `file_out`
49 fun generate
50 do
51 # License
52 file_out.write license
53
54 # Module declaration
55 var module_name = module_name
56 if module_name != null then file_out.write "module {module_name} is no_warning(\"useless-superclass\")\n"
57 file_out.write "\n"
58
59 # All importations
60 var imports = new HashSet[String]
61 imports.add "import java\n"
62 for key, jclass in model.classes do
63 for import_ in jclass.imports do imports.add "import android::{import_}\n"
64 end
65 file_out.write imports.join("\n")
66 file_out.write "\n"
67
68 # Sort classes from top-level classes (java.lang.Object) to leaves
69 var standard_classes = new Array[JavaClass]
70 for name, jclass in model.classes do
71 if not jclass.class_type.is_anonymous then standard_classes.add jclass
72 end
73 var linearized = model.class_hierarchy.linearize(standard_classes)
74
75 for jclass in linearized do
76 # Skip classes with an invalid name at the Java language level
77 if jclass.class_type.extern_equivalent.has("-") then continue
78
79 generate_class_header(jclass)
80
81 if not sys.opt_no_properties.value then
82
83 for id, signatures in jclass.local_intro_methods do
84 for signature in signatures do
85 assert not signature.is_static
86 generate_method(jclass, id, id, signature.return_type, signature.params)
87 file_out.write "\n"
88 end
89 end
90
91 # Constructors
92 for constructor in jclass.constructors do
93 var complex = jclass.constructors.length != 1 and constructor.params.not_empty
94 var base_name = if complex then "from" else ""
95 var name = jclass.nit_name_for(base_name, constructor.params, complex, false, local_only=true)
96
97 generate_constructor(jclass, constructor, name)
98 end
99
100 # Attributes
101 for id, attribute in jclass.attributes do if not attribute.is_static then
102 generate_getter_setter(jclass, id, attribute)
103 end
104 end
105
106 # JNI services
107 generate_jni_services jclass.class_type
108
109 # Close the class
110 file_out.write "end\n\n"
111
112 if not sys.opt_no_properties.value then
113
114 # Static functions as top-level methods
115 var static_functions_prefix = jclass.class_type.extern_name.to_snake_case
116 for id, signatures in jclass.methods do
117 for signature in signatures do if signature.is_static then
118 var nit_id = static_functions_prefix + "_" + id
119 generate_method(jclass, id, nit_id, signature.return_type, signature.params, is_static=true)
120 file_out.write "\n"
121 end
122 end
123
124 # Static attributes as top-level getters and setters
125 for id, attribute in jclass.attributes do if attribute.is_static then
126 generate_getter_setter(jclass, id, attribute)
127 end
128 end
129
130 # Primitive arrays
131 for d in [1..opt_arrays.value] do
132 generate_primitive_array(jclass, d)
133 end
134 end
135
136 if stub_for_unknown_types then
137 for jtype, nit_type in model.unknown_types do
138 generate_unknown_class_header(jtype)
139 file_out.write "\n"
140 end
141 end
142
143 file_out.close
144 end
145
146 # Serialize `model` to a file next to `file_name`
147 fun write_model_to_file
148 do
149 # Write the model to file next to the Nit module
150 var model_path = file_name.strip_extension + ".jwrapper.bin"
151 var model_stream = model_path.to_path.open_wo
152 var serializer = new BinarySerializer(model_stream)
153 serializer.serialize model
154 model_stream.close
155 end
156
157 # License for the header of the generated Nit module
158 var license = """
159 # This file is part of NIT (http://www.nitlanguage.org).
160 #
161 # Licensed under the Apache License, Version 2.0 (the "License");
162 # you may not use this file except in compliance with the License.
163 # You may obtain a copy of the License at
164 #
165 # http://www.apache.org/licenses/LICENSE-2.0
166 #
167 # Unless required by applicable law or agreed to in writing, software
168 # distributed under the License is distributed on an "AS IS" BASIS,
169 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
170 # See the License for the specific language governing permissions and
171 # limitations under the License.
172
173 # This code has been generated using `jwrapper`
174 """ is writable
175
176 private fun generate_class_header(java_class: JavaClass)
177 do
178 var java_type = java_class.class_type
179 var nit_type = model.java_to_nit_type(java_type)
180
181 var super_java_types = new HashSet[JavaType]
182 super_java_types.add_all java_class.extends
183 super_java_types.add_all java_class.implements
184
185 var supers = new Array[String]
186 var effective_supers = 0
187 for java_super in super_java_types do
188 var nit_super = model.java_to_nit_type(java_super)
189
190 # Comment out unknown types
191 var c = ""
192 if not nit_super.is_known and comment_unknown_types then c = "# "
193
194 supers.add "{c}super {nit_super}"
195 if c != "# " then effective_supers += 1
196 end
197
198 if effective_supers == 0 then
199 if java_class.class_type.package_name == "java.lang.Object" then
200 supers.add "super JavaObject"
201 else supers.add "super Java_lang_Object"
202 end
203
204 file_out.write """
205 # Java class: {{{java_type}}}
206 extern class {{{nit_type}}} in "Java" `{ {{{java_type.extern_equivalent}}} `}
207 {{{supers.join("\n\t")}}}
208
209 """
210 end
211
212 private fun generate_unknown_class_header(jtype: JavaType)
213 do
214 var nit_type = jtype.extern_name
215
216 file_out.write "extern class {nit_type} in \"Java\" `\{ {jtype.extern_equivalent} `\}\n"
217 file_out.write "\tsuper JavaObject\n\nend\n"
218 end
219
220 private fun generate_method(java_class: JavaClass, java_method_id, method_id: String,
221 java_return_type: JavaType, java_params: Array[JavaType], is_static: nullable Bool)
222 do
223 var java_args = new Array[String]
224 var nit_params = new Array[String]
225 var nit_id = "arg"
226 var nit_id_no = 0
227 var c = ""
228
229 # Parameters
230 for jparam in java_params do
231 var nit_type = model.java_to_nit_type(jparam)
232
233 if not nit_type.is_known and comment_unknown_types then c = "#"
234 if jparam.is_vararg then c = "#"
235
236 java_args.add "{jparam.param_cast}{nit_id}{nit_id_no}"
237 nit_params.add "{nit_id}{nit_id_no}: {nit_type}"
238
239 nit_id_no += 1
240 end
241
242 # Method identifier
243 method_id = method_id.to_nit_method_name
244 method_id = java_class.nit_name_for(method_id, java_params, java_class.methods[java_method_id].length > 1, is_static == true)
245
246 # Build the signature
247 var nit_signature = new Array[String]
248 nit_signature.add "fun {method_id}"
249 if not java_params.is_empty then nit_signature.add "({nit_params.join(", ")})"
250
251 # Return value
252 var return_type = null
253 if not java_return_type.is_void then
254 return_type = model.java_to_nit_type(java_return_type)
255
256 if not return_type.is_known and comment_unknown_types then c = "#"
257 if java_return_type.is_vararg then c = "#"
258
259 nit_signature.add ": " + return_type.to_s
260 end
261
262 # Build the call in Java
263 var java_call
264 if is_static == true then
265 java_call = java_class.class_type.package_name
266 else java_call = "self"
267 java_call += ".{java_method_id}({java_args.join(", ")})"
268
269 if return_type != null then java_call = "return {java_return_type.return_cast}" + java_call
270
271 # Tabulation
272 var t = "\t"
273 if is_static == true then t = ""
274 var ct = c+t
275
276 # Write
277 file_out.write """
278 {{{t}}}# Java implementation: {{{java_return_type}}} {{{java_class}}}.{{{java_method_id}}}({{{java_params.join(", ")}}})
279 {{{ct}}}{{{nit_signature.join}}} in "Java" `{
280 {{{ct}}} {{{java_call}}};
281 {{{ct}}}`}
282 """
283 end
284
285 # Generate getter and setter to access an attribute, of field
286 private fun generate_getter_setter(java_class: JavaClass, java_id: String,
287 attribute: JavaAttribute)
288 do
289 var java_type = attribute.java_type
290 var nit_type = model.java_to_nit_type(java_type)
291
292 var nit_id = java_id
293 if attribute.is_static then nit_id = java_class.class_type.extern_name.to_snake_case + "_" + nit_id
294 nit_id = nit_id.to_nit_method_name
295 nit_id = java_class.nit_name_for(nit_id, [java_type], false, attribute.is_static)
296
297 var c = ""
298 if not nit_type.is_known and comment_unknown_types then c = "#"
299 if java_type.is_vararg then c = "#"
300
301 var recv
302 if attribute.is_static then
303 recv = java_class.class_type.package_name
304 else recv = "self"
305
306 # Tabulation
307 var t = "\t"
308 if attribute.is_static then t = ""
309 var ct = c+t
310
311 file_out.write """
312 {{{t}}}# Java getter: {{{java_class}}}.{{{java_id}}}
313 {{{ct}}}fun {{{nit_id}}}: {{{nit_type}}} in "Java" `{
314 {{{ct}}} return {{{recv}}}.{{{java_id}}};
315 {{{ct}}}`}
316
317 {{{t}}}# Java setter: {{{java_class}}}.{{{java_id}}}
318 {{{ct}}}fun {{{nit_id}}}=(value: {{{nit_type}}}) in "Java" `{
319 {{{ct}}} {{{recv}}}.{{{java_id}}} = value;
320 {{{ct}}}`}
321
322 """
323 end
324
325 # Generate getter and setter to access an attribute, of field
326 private fun generate_constructor(java_class: JavaClass, constructor: JavaConstructor, name: String)
327 do
328 var c = ""
329 var nit_params_s = ""
330 var java_params_s = ""
331
332 if constructor.params.not_empty then
333 var nit_params = new Array[String]
334 var java_params = new Array[String]
335 var param_id = 'a'
336 for java_type in constructor.params do
337
338 java_params.add "{java_type.param_cast}{param_id}"
339
340 var nit_type = model.java_to_nit_type(java_type)
341 nit_params.add "{param_id}: {nit_type}"
342 param_id = param_id.successor(1)
343
344 if not nit_type.is_known and comment_unknown_types then c = "#"
345 if java_type.is_vararg then c = "#"
346 end
347
348 nit_params_s = "(" + nit_params.join(", ") + ")"
349 java_params_s = java_params.join(", ")
350 end
351
352 file_out.write """
353 # Java constructor: {{{java_class}}}
354 {{{c}}} new {{{name}}}{{{nit_params_s}}} in "Java" `{
355 {{{c}}} return new {{{java_class.class_type.package_name}}}({{{java_params_s}}});
356 {{{c}}} `}
357
358 """
359 end
360
361 private fun generate_primitive_array(java_class: JavaClass, dimensions: Int)
362 do
363 var base_java_type = java_class.class_type
364 var java_type = base_java_type.clone
365 java_type.array_dimension = dimensions
366
367 var base_nit_type = model.java_to_nit_type(base_java_type)
368 var nit_type = model.java_to_nit_type(java_type)
369
370 file_out.write """
371 # Java primitive array: {{{java_type}}}
372 extern class {{{nit_type}}} in "Java" `{ {{{java_type.extern_equivalent}}} `}
373 super AbstractJavaArray[{{{base_nit_type}}}]
374
375 # Get a new array of the given `size`
376 new(size: Int) in "Java" `{ return new {{{base_java_type}}}[(int)size]; `}
377
378 redef fun [](i) in "Java" `{ return self[(int)i]; `}
379
380 redef fun []=(i, e) in "Java" `{ self[(int)i] = e; `}
381
382 redef fun length in "Java" `{ return self.length; `}
383
384 """
385 generate_jni_services(java_type)
386 file_out.write """
387 end
388
389 """
390 end
391
392 # Generate JNI related services
393 #
394 # For now, mostly avoid issue #845, but more services could be generated as needed.
395 private fun generate_jni_services(java_type: JavaType)
396 do
397 var nit_type = model.java_to_nit_type(java_type)
398
399 file_out.write """
400 redef fun new_global_ref import sys, Sys.jni_env `{
401 Sys sys = {{{nit_type}}}_sys(self);
402 JNIEnv *env = Sys_jni_env(sys);
403 return (*env)->NewGlobalRef(env, self);
404 `}
405
406 redef fun pop_from_local_frame_with_env(jni_env) `{
407 return (*jni_env)->PopLocalFrame(jni_env, self);
408 `}
409 """
410 end
411 end
412
413 redef class Sys
414 # List of Nit keywords
415 #
416 # These may also be keywords in Java, but there they would be used capitalized.
417 private var nit_keywords = new HashSet[String].from(["abort", "abstract", "and", "assert",
418 "break", "class", "continue", "do", "else", "end", "enum", "extern", "false", "implies",
419 "import", "init", "interface", "intrude", "if", "in", "is", "isa", "isset", "for", "label",
420 "loop", "module", "new", "not", "null", "nullable", "or", "package", "private",
421 "protected", "public", "return", "self", "super", "then", "true", "type", "var", "while",
422
423 # Top-level methods
424 "class_name", "get_time", "hash", "inspect", "inspect_head", "is_same_type",
425 "is_same_instance", "object_id", "output", "output_class_name", "sys", "to_s",
426
427 # Pointer or JavaObject methods
428 "free"])
429
430 # Name of methods used at the top-level
431 #
432 # Used by `JavaClass::nit_name_for` with static properties.
433 private var top_level_used_names = new HashSet[String]
434
435 # Option to _not_ generate properties (static or from classes)
436 var opt_no_properties = new OptionBool("Do not wrap properties, only classes and basic services", "-n", "--no-properties")
437 end
438
439 redef class String
440
441 # Convert the Java method name `self` to the Nit style
442 #
443 # * Converts to snake case
444 # * Strips `Get` and `Set`
445 # * Add suffix `=` to setters
446 fun to_nit_method_name: String
447 do
448 var name = self.to_snake_case
449
450 # Strip the '_' prefix
451 while name.has_prefix("_") do name = name.substring(1, name.length-1)
452
453 # Escape Nit keywords
454 if nit_keywords.has(name) then name += "_"
455
456 # If the name starts by something other than a letter, prefix with `java_`
457 if not name.chars.first.is_letter then name = "java_" + name
458
459 name = name.replace("$", "_")
460
461 return name
462 end
463 end
464
465 redef class JavaClass
466 # Property names used in this class
467 private var used_names = new HashSet[String] is serialize
468
469 # Get an available property name for the Java property with `name` and parameters
470 #
471 # If `use_parameters_name` then expect that there will be conflicts,
472 # so use the types of `parameters` to build the name.
473 private fun nit_name_for(name: String, parameters: Array[JavaType], use_parameters_name: Bool, is_static: Bool, local_only: nullable Bool): String
474 do
475 # Append the name of each parameter
476 if use_parameters_name then
477 for param in parameters do
478 var id = param.id
479 id += "Array"*param.array_dimension
480 name += "_" + id
481 end
482 end
483
484 # Set of sets of property names, local or top-level
485 var local_used_names
486 var used_names
487 if is_static then
488 # Top-level methods
489 local_used_names = sys.top_level_used_names
490 used_names = sys.top_level_used_names
491 else if local_only == true then
492 # Local only: constructors
493 local_used_names = self.used_names
494 used_names = self.used_names
495 else
496 # Avoid conflicts with all super classes
497 local_used_names = self.used_names
498 used_names = new HashSet[String]
499 for sup in in_hierarchy.greaters do
500 used_names.add_all sup.used_names
501 end
502 end
503
504 # As a last resort, append numbers to the name
505 var base_name = name
506 var count = 1
507 while used_names.has(name) do
508 name = base_name + count.to_s
509 count += 1
510 end
511
512 local_used_names.add name
513 return name
514 end
515 end