e716a2e3860cbebe8a2ab0afdaa397f274eeeb21
[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 if not sys.opt_save_model.value then return
150
151 # Write the model to file next to the Nit module
152 var model_path = file_name.strip_extension + ".jwrapper.bin"
153 var model_stream = model_path.to_path.open_wo
154 var serializer = new BinarySerializer(model_stream)
155 serializer.serialize model
156 model_stream.close
157 end
158
159 # License for the header of the generated Nit module
160 var license = """
161 # This file is part of NIT (http://www.nitlanguage.org).
162 #
163 # Licensed under the Apache License, Version 2.0 (the "License");
164 # you may not use this file except in compliance with the License.
165 # You may obtain a copy of the License at
166 #
167 # http://www.apache.org/licenses/LICENSE-2.0
168 #
169 # Unless required by applicable law or agreed to in writing, software
170 # distributed under the License is distributed on an "AS IS" BASIS,
171 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
172 # See the License for the specific language governing permissions and
173 # limitations under the License.
174
175 # This code has been generated using `jwrapper`
176 """ is writable
177
178 private fun generate_class_header(java_class: JavaClass)
179 do
180 var java_type = java_class.class_type
181 var nit_type = model.java_to_nit_type(java_type)
182
183 var super_java_types = new HashSet[JavaType]
184 super_java_types.add_all java_class.extends
185 super_java_types.add_all java_class.implements
186
187 var supers = new Array[String]
188 var effective_supers = 0
189 for java_super in super_java_types do
190 var nit_super = model.java_to_nit_type(java_super)
191
192 # Comment out unknown types
193 var c = ""
194 if not nit_super.is_known and comment_unknown_types then c = "# "
195
196 supers.add "{c}super {nit_super}"
197 if c != "# " then effective_supers += 1
198 end
199
200 if effective_supers == 0 then
201 if java_class.class_type.package_name == "java.lang.Object" or
202 not model.knows_the_object_class then
203 supers.add "super JavaObject"
204 else supers.add "super Java_lang_Object"
205 end
206
207 file_out.write """
208 # Java class: {{{java_type}}}
209 extern class {{{nit_type}}} in "Java" `{ {{{java_type.extern_equivalent}}} `}
210 {{{supers.join("\n\t")}}}
211
212 """
213 end
214
215 private fun generate_unknown_class_header(jtype: JavaType)
216 do
217 var nit_type = jtype.extern_name
218
219 file_out.write "extern class {nit_type} in \"Java\" `\{ {jtype.extern_equivalent} `\}\n"
220 file_out.write "\tsuper JavaObject\n\nend\n"
221 end
222
223 private fun generate_method(java_class: JavaClass, java_method_id, method_id: String,
224 java_return_type: JavaType, java_params: Array[JavaType], is_static: nullable Bool)
225 do
226 var java_args = new Array[String]
227 var nit_params = new Array[String]
228 var nit_id = "arg"
229 var nit_id_no = 0
230 var c = ""
231
232 # Parameters
233 for jparam in java_params do
234 var nit_type = model.java_to_nit_type(jparam)
235
236 if not nit_type.is_known and comment_unknown_types then c = "#"
237 if jparam.is_vararg then c = "#"
238
239 java_args.add "{jparam.param_cast}{nit_id}{nit_id_no}"
240 nit_params.add "{nit_id}{nit_id_no}: {nit_type}"
241
242 nit_id_no += 1
243 end
244
245 # Method identifier
246 method_id = method_id.to_nit_method_name
247 method_id = java_class.nit_name_for(method_id, java_params, java_class.methods[java_method_id].length > 1, is_static == true)
248
249 # Build the signature
250 var nit_signature = new Array[String]
251 nit_signature.add "fun {method_id}"
252 if not java_params.is_empty then nit_signature.add "({nit_params.join(", ")})"
253
254 # Return value
255 var return_type = null
256 if not java_return_type.is_void then
257 return_type = model.java_to_nit_type(java_return_type)
258
259 if not return_type.is_known and comment_unknown_types then c = "#"
260 if java_return_type.is_vararg then c = "#"
261
262 nit_signature.add ": " + return_type.to_s
263 end
264
265 # Build the call in Java
266 var java_call
267 if is_static == true then
268 java_call = java_class.class_type.package_name
269 else java_call = "self"
270 java_call += ".{java_method_id}({java_args.join(", ")})"
271
272 if return_type != null then java_call = "return {java_return_type.return_cast}" + java_call
273
274 # Tabulation
275 var t = "\t"
276 if is_static == true then t = ""
277 var ct = c+t
278
279 # Write
280 file_out.write """
281 {{{t}}}# Java implementation: {{{java_return_type}}} {{{java_class}}}.{{{java_method_id}}}({{{java_params.join(", ")}}})
282 {{{ct}}}{{{nit_signature.join}}} in "Java" `{
283 {{{ct}}} {{{java_call}}};
284 {{{ct}}}`}
285 """
286 end
287
288 # Generate getter and setter to access an attribute, of field
289 private fun generate_getter_setter(java_class: JavaClass, java_id: String,
290 attribute: JavaAttribute)
291 do
292 var java_type = attribute.java_type
293 var nit_type = model.java_to_nit_type(java_type)
294
295 var nit_id = java_id
296 if attribute.is_static then nit_id = java_class.class_type.extern_name.to_snake_case + "_" + nit_id
297 nit_id = nit_id.to_nit_method_name
298 nit_id = java_class.nit_name_for(nit_id, [java_type], false, attribute.is_static)
299
300 var c = ""
301 if not nit_type.is_known and comment_unknown_types then c = "#"
302 if java_type.is_vararg then c = "#"
303
304 var recv
305 if attribute.is_static then
306 recv = java_class.class_type.package_name
307 else recv = "self"
308
309 # Tabulation
310 var t = "\t"
311 if attribute.is_static then t = ""
312 var ct = c+t
313
314 file_out.write """
315 {{{t}}}# Java getter: {{{java_class}}}.{{{java_id}}}
316 {{{ct}}}fun {{{nit_id}}}: {{{nit_type}}} in "Java" `{
317 {{{ct}}} return {{{recv}}}.{{{java_id}}};
318 {{{ct}}}`}
319
320 {{{t}}}# Java setter: {{{java_class}}}.{{{java_id}}}
321 {{{ct}}}fun {{{nit_id}}}=(value: {{{nit_type}}}) in "Java" `{
322 {{{ct}}} {{{recv}}}.{{{java_id}}} = value;
323 {{{ct}}}`}
324
325 """
326 end
327
328 # Generate getter and setter to access an attribute, of field
329 private fun generate_constructor(java_class: JavaClass, constructor: JavaConstructor, name: String)
330 do
331 var c = ""
332 var nit_params_s = ""
333 var java_params_s = ""
334
335 if constructor.params.not_empty then
336 var nit_params = new Array[String]
337 var java_params = new Array[String]
338 var param_id = 'a'
339 for java_type in constructor.params do
340
341 java_params.add "{java_type.param_cast}{param_id}"
342
343 var nit_type = model.java_to_nit_type(java_type)
344 nit_params.add "{param_id}: {nit_type}"
345 param_id = param_id.successor(1)
346
347 if not nit_type.is_known and comment_unknown_types then c = "#"
348 if java_type.is_vararg then c = "#"
349 end
350
351 nit_params_s = "(" + nit_params.join(", ") + ")"
352 java_params_s = java_params.join(", ")
353 end
354
355 file_out.write """
356 # Java constructor: {{{java_class}}}
357 {{{c}}} new {{{name}}}{{{nit_params_s}}} in "Java" `{
358 {{{c}}} return new {{{java_class.class_type.package_name}}}({{{java_params_s}}});
359 {{{c}}} `}
360
361 """
362 end
363
364 private fun generate_primitive_array(java_class: JavaClass, dimensions: Int)
365 do
366 var base_java_type = java_class.class_type
367 var java_type = base_java_type.clone
368 java_type.array_dimension = dimensions
369
370 var base_nit_type = model.java_to_nit_type(base_java_type)
371 var nit_type = model.java_to_nit_type(java_type)
372
373 file_out.write """
374 # Java primitive array: {{{java_type}}}
375 extern class {{{nit_type}}} in "Java" `{ {{{java_type.extern_equivalent}}} `}
376 super AbstractJavaArray[{{{base_nit_type}}}]
377
378 # Get a new array of the given `size`
379 new(size: Int) in "Java" `{ return new {{{base_java_type}}}[(int)size]; `}
380
381 redef fun [](i) in "Java" `{ return self[(int)i]; `}
382
383 redef fun []=(i, e) in "Java" `{ self[(int)i] = e; `}
384
385 redef fun length in "Java" `{ return self.length; `}
386
387 """
388 generate_jni_services(java_type)
389 file_out.write """
390 end
391
392 """
393 end
394
395 # Generate JNI related services
396 #
397 # For now, mostly avoid issue #845, but more services could be generated as needed.
398 private fun generate_jni_services(java_type: JavaType)
399 do
400 var nit_type = model.java_to_nit_type(java_type)
401
402 file_out.write """
403 redef fun new_global_ref import sys, Sys.jni_env `{
404 Sys sys = {{{nit_type}}}_sys(self);
405 JNIEnv *env = Sys_jni_env(sys);
406 return (*env)->NewGlobalRef(env, self);
407 `}
408
409 redef fun pop_from_local_frame_with_env(jni_env) `{
410 return (*jni_env)->PopLocalFrame(jni_env, self);
411 `}
412 """
413 end
414 end
415
416 redef class Sys
417 # List of Nit keywords
418 #
419 # These may also be keywords in Java, but there they would be used capitalized.
420 private var nit_keywords = new HashSet[String].from(["abort", "abstract", "and", "assert",
421 "break", "class", "continue", "do", "else", "end", "enum", "extern", "false", "implies",
422 "import", "init", "interface", "intrude", "if", "in", "is", "isa", "isset", "for", "label",
423 "loop", "module", "new", "not", "null", "nullable", "or", "package", "private",
424 "protected", "public", "return", "self", "super", "then", "true", "type", "var", "while",
425
426 # Top-level methods
427 "class_name", "get_time", "hash", "inspect", "inspect_head", "is_same_type",
428 "is_same_instance", "object_id", "output", "output_class_name", "sys", "to_s",
429
430 # Pointer or JavaObject methods
431 "free"])
432
433 # Name of methods used at the top-level
434 #
435 # Used by `JavaClass::nit_name_for` with static properties.
436 private var top_level_used_names = new HashSet[String]
437
438 # Option to _not_ generate properties (static or from classes)
439 var opt_no_properties = new OptionBool("Do not wrap properties, only classes and basic services", "-n", "--no-properties")
440
441 # Should the model be serialized to a file?
442 var opt_save_model = new OptionBool("Save the model next to the generated Nit module", "-s", "--save-model")
443 end
444
445 redef class String
446
447 # Convert the Java method name `self` to the Nit style
448 #
449 # * Converts to snake case
450 # * Strips `Get` and `Set`
451 # * Add suffix `=` to setters
452 fun to_nit_method_name: String
453 do
454 var name = self.to_snake_case
455
456 # Strip the '_' prefix
457 while name.has_prefix("_") do name = name.substring(1, name.length-1)
458
459 # Escape Nit keywords
460 if nit_keywords.has(name) then name += "_"
461
462 # If the name starts by something other than a letter, prefix with `java_`
463 if not name.chars.first.is_letter then name = "java_" + name
464
465 name = name.replace("$", "_")
466
467 return name
468 end
469 end
470
471 redef class JavaClass
472 # Property names used in this class
473 private var used_names = new HashSet[String] is serialize
474
475 # Get an available property name for the Java property with `name` and parameters
476 #
477 # If `use_parameters_name` then expect that there will be conflicts,
478 # so use the types of `parameters` to build the name.
479 private fun nit_name_for(name: String, parameters: Array[JavaType], use_parameters_name: Bool, is_static: Bool, local_only: nullable Bool): String
480 do
481 # Append the name of each parameter
482 if use_parameters_name then
483 for param in parameters do
484 var id = param.id
485 id += "Array"*param.array_dimension
486 name += "_" + id
487 end
488 end
489
490 # Set of sets of property names, local or top-level
491 var local_used_names
492 var used_names
493 if is_static then
494 # Top-level methods
495 local_used_names = sys.top_level_used_names
496 used_names = sys.top_level_used_names
497 else if local_only == true then
498 # Local only: constructors
499 local_used_names = self.used_names
500 used_names = self.used_names
501 else
502 # Avoid conflicts with all super classes
503 local_used_names = self.used_names
504 used_names = new HashSet[String]
505 for sup in in_hierarchy.greaters do
506 used_names.add_all sup.used_names
507 end
508 end
509
510 # As a last resort, append numbers to the name
511 var base_name = name
512 var count = 1
513 while used_names.has(name) do
514 name = base_name + count.to_s
515 count += 1
516 end
517
518 local_used_names.add name
519 return name
520 end
521 end