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