contrib/jwrapper: close module file stream after writing
[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}\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 for key, jclass in model.classes do
69 # Skip anonymous classes
70 if jclass.class_type.is_anonymous then continue
71
72 # Skip classes with an invalid name at the Java language level
73 if jclass.class_type.extern_equivalent.has("-") then continue
74
75 generate_class_header(jclass.class_type)
76
77 for id, signatures in jclass.methods do
78 for signature in signatures do if not signature.is_static then
79 generate_method(jclass, id, id, signature.return_type, signature.params)
80 file_out.write "\n"
81 end
82 end
83
84 # Constructors
85 for constructor in jclass.constructors do
86 var complex = jclass.constructors.length != 1 and constructor.params.not_empty
87 var base_name = if complex then "from" else ""
88 var name = jclass.nit_name_for(base_name, constructor.params, complex, false)
89
90 generate_constructor(jclass, constructor, name)
91 end
92
93 # Attributes
94 for id, attribute in jclass.attributes do if not attribute.is_static then
95 generate_getter_setter(jclass, id, attribute)
96 end
97
98 # JNI services
99 generate_jni_services jclass.class_type
100
101 # Close the class
102 file_out.write "end\n\n"
103
104 # Static functions as top-level methods
105 var static_functions_prefix = jclass.class_type.extern_name.to_snake_case
106 for id, signatures in jclass.methods do
107 for signature in signatures do if signature.is_static then
108 var nit_id = static_functions_prefix + "_" + id
109 generate_method(jclass, id, nit_id, signature.return_type, signature.params, is_static=true)
110 file_out.write "\n"
111 end
112 end
113
114 # Static attributes as top-level getters and setters
115 for id, attribute in jclass.attributes do if attribute.is_static then
116 generate_getter_setter(jclass, id, attribute)
117 end
118
119 # Primitive arrays
120 for d in [1..opt_arrays.value] do
121 generate_primitive_array(jclass, d)
122 end
123 end
124
125 if stub_for_unknown_types then
126 for jtype, nit_type in model.unknown_types do
127 generate_unknown_class_header(jtype)
128 file_out.write "\n"
129 end
130 end
131
132 file_out.close
133 end
134
135 # License for the header of the generated Nit module
136 var license = """
137 # This file is part of NIT (http://www.nitlanguage.org).
138 #
139 # Licensed under the Apache License, Version 2.0 (the "License");
140 # you may not use this file except in compliance with the License.
141 # You may obtain a copy of the License at
142 #
143 # http://www.apache.org/licenses/LICENSE-2.0
144 #
145 # Unless required by applicable law or agreed to in writing, software
146 # distributed under the License is distributed on an "AS IS" BASIS,
147 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
148 # See the License for the specific language governing permissions and
149 # limitations under the License.
150
151 # This code has been generated using `jwrapper`
152 """ is writable
153
154 private fun generate_class_header(jtype: JavaType)
155 do
156 var nit_type = model.java_to_nit_type(jtype)
157 file_out.write "# Java class: {jtype}\n"
158 file_out.write "extern class {nit_type} in \"Java\" `\{ {jtype.extern_equivalent} `\}\n"
159 file_out.write "\tsuper JavaObject\n\n"
160 end
161
162 private fun generate_unknown_class_header(jtype: JavaType)
163 do
164 var nit_type = jtype.extern_name
165
166 file_out.write "extern class {nit_type} in \"Java\" `\{ {jtype.extern_equivalent} `\}\n"
167 file_out.write "\tsuper JavaObject\n\nend\n"
168 end
169
170 private fun generate_method(java_class: JavaClass, java_method_id, method_id: String,
171 java_return_type: JavaType, java_params: Array[JavaType], is_static: nullable Bool)
172 do
173 var java_args = new Array[String]
174 var nit_params = new Array[String]
175 var nit_id = "arg"
176 var nit_id_no = 0
177 var c = ""
178
179 # Parameters
180 for jparam in java_params do
181 var nit_type = model.java_to_nit_type(jparam)
182
183 if not nit_type.is_known and comment_unknown_types then c = "#"
184 if jparam.is_vararg then c = "#"
185
186 java_args.add "{jparam.param_cast}{nit_id}{nit_id_no}"
187 nit_params.add "{nit_id}{nit_id_no}: {nit_type}"
188
189 nit_id_no += 1
190 end
191
192 # Method identifier
193 method_id = method_id.to_nit_method_name
194 method_id = java_class.nit_name_for(method_id, java_params, java_class.methods[java_method_id].length > 1, is_static == true)
195
196 # Build the signature
197 var nit_signature = new Array[String]
198 nit_signature.add "fun {method_id}"
199 if not java_params.is_empty then nit_signature.add "({nit_params.join(", ")})"
200
201 # Return value
202 var return_type = null
203 if not java_return_type.is_void then
204 return_type = model.java_to_nit_type(java_return_type)
205
206 if not return_type.is_known and comment_unknown_types then c = "#"
207 if java_return_type.is_vararg then c = "#"
208
209 nit_signature.add ": " + return_type.to_s
210 end
211
212 # Build the call in Java
213 var java_call
214 if is_static == true then
215 java_call = java_class.class_type.package_name
216 else java_call = "self"
217 java_call += ".{java_method_id}({java_args.join(", ")})"
218
219 if return_type != null then java_call = "return {java_return_type.return_cast}" + java_call
220
221 # Tabulation
222 var t = "\t"
223 if is_static == true then t = ""
224 var ct = c+t
225
226 # Write
227 file_out.write """
228 {{{t}}}# Java implementation: {{{java_return_type}}} {{{java_class}}}.{{{java_method_id}}}({{{java_params.join(", ")}}})
229 {{{ct}}}{{{nit_signature.join}}} in "Java" `{
230 {{{ct}}} {{{java_call}}};
231 {{{ct}}}`}
232 """
233 end
234
235 # Generate getter and setter to access an attribute, of field
236 private fun generate_getter_setter(java_class: JavaClass, java_id: String,
237 attribute: JavaAttribute)
238 do
239 var java_type = attribute.java_type
240 var nit_type = model.java_to_nit_type(java_type)
241
242 var nit_id = java_id
243 if attribute.is_static then nit_id = java_class.class_type.extern_name.to_snake_case + "_" + nit_id
244 nit_id = nit_id.to_nit_method_name
245 nit_id = java_class.nit_name_for(nit_id, [java_type], false, attribute.is_static)
246
247 var c = ""
248 if not nit_type.is_known and comment_unknown_types then c = "#"
249 if java_type.is_vararg then c = "#"
250
251 var recv
252 if attribute.is_static then
253 recv = java_class.class_type.package_name
254 else recv = "self"
255
256 # Tabulation
257 var t = "\t"
258 if attribute.is_static then t = ""
259 var ct = c+t
260
261 file_out.write """
262 {{{t}}}# Java getter: {{{java_class}}}.{{{java_id}}}
263 {{{ct}}}fun {{{nit_id}}}: {{{nit_type}}} in "Java" `{
264 {{{ct}}} return {{{recv}}}.{{{java_id}}};
265 {{{ct}}}`}
266
267 {{{t}}}# Java setter: {{{java_class}}}.{{{java_id}}}
268 {{{ct}}}fun {{{nit_id}}}=(value: {{{nit_type}}}) in "Java" `{
269 {{{ct}}} {{{recv}}}.{{{java_id}}} = value;
270 {{{ct}}}`}
271
272 """
273 end
274
275 # Generate getter and setter to access an attribute, of field
276 private fun generate_constructor(java_class: JavaClass, constructor: JavaConstructor, name: String)
277 do
278 var c = ""
279 var nit_params_s = ""
280 var java_params_s = ""
281
282 if constructor.params.not_empty then
283 var nit_params = new Array[String]
284 var java_params = new Array[String]
285 var param_id = 'a'
286 for java_type in constructor.params do
287
288 java_params.add "{java_type.param_cast}{param_id}"
289
290 var nit_type = model.java_to_nit_type(java_type)
291 nit_params.add "{param_id}: {nit_type}"
292 param_id = param_id.successor(1)
293
294 if not nit_type.is_known and comment_unknown_types then c = "#"
295 if java_type.is_vararg then c = "#"
296 end
297
298 nit_params_s = "(" + nit_params.join(", ") + ")"
299 java_params_s = java_params.join(", ")
300 end
301
302 file_out.write """
303 # Java constructor: {{{java_class}}}
304 {{{c}}} new {{{name}}}{{{nit_params_s}}} in "Java" `{
305 {{{c}}} return new {{{java_class.class_type.package_name}}}({{{java_params_s}}});
306 {{{c}}} `}
307
308 """
309 end
310
311 private fun generate_primitive_array(java_class: JavaClass, dimensions: Int)
312 do
313 var base_java_type = java_class.class_type
314 var java_type = base_java_type.clone
315 java_type.array_dimension = dimensions
316
317 var base_nit_type = model.java_to_nit_type(base_java_type)
318 var nit_type = model.java_to_nit_type(java_type)
319
320 file_out.write """
321 # Java primitive array: {{{java_type}}}
322 extern class {{{nit_type}}} in "Java" `{ {{{java_type.extern_equivalent}}} `}
323 super AbstractJavaArray[{{{base_nit_type}}}]
324
325 # Get a new array of the given `size`
326 new(size: Int) in "Java" `{ return new {{{base_java_type}}}[(int)size]; `}
327
328 redef fun [](i) in "Java" `{ return self[(int)i]; `}
329
330 redef fun []=(i, e) in "Java" `{ self[(int)i] = e; `}
331
332 redef fun length in "Java" `{ return self.length; `}
333
334 """
335 generate_jni_services(java_type)
336 file_out.write """
337 end
338
339 """
340 end
341
342 # Generate JNI related services
343 #
344 # For now, mostly avoid issue #845, but more services could be generated as needed.
345 private fun generate_jni_services(java_type: JavaType)
346 do
347 var nit_type = model.java_to_nit_type(java_type)
348
349 file_out.write """
350 redef fun new_global_ref import sys, Sys.jni_env `{
351 Sys sys = {{{nit_type}}}_sys(self);
352 JNIEnv *env = Sys_jni_env(sys);
353 return (*env)->NewGlobalRef(env, self);
354 `}
355
356 redef fun pop_from_local_frame_with_env(jni_env) `{
357 return (*jni_env)->PopLocalFrame(jni_env, self);
358 `}
359 """
360 end
361 end
362
363 redef class Sys
364 # List of Nit keywords
365 #
366 # These may also be keywords in Java, but there they would be used capitalized.
367 private var nit_keywords = new HashSet[String].from(["abort", "abstract", "and", "assert",
368 "break", "class", "continue", "do", "else", "end", "enum", "extern", "false", "implies",
369 "import", "init", "interface", "intrude", "if", "in", "is", "isa", "isset", "for", "label",
370 "loop", "module", "new", "not", "null", "nullable", "or", "package", "private",
371 "protected", "public", "return", "self", "super", "then", "true", "type", "var", "while",
372
373 # Top-level methods
374 "class_name", "get_time", "hash", "inspect", "inspect_head", "is_same_type",
375 "is_same_instance", "object_id", "output", "output_class_name", "sys", "to_s",
376
377 # Pointer or JavaObject methods
378 "free"])
379
380 # Name of methods used at the top-level
381 #
382 # Used by `JavaClass::nit_name_for` with static properties.
383 private var top_level_used_names = new HashSet[String]
384 end
385
386 redef class String
387
388 # Convert the Java method name `self` to the Nit style
389 #
390 # * Converts to snake case
391 # * Strips `Get` and `Set`
392 # * Add suffix `=` to setters
393 fun to_nit_method_name: String
394 do
395 var name = self.to_snake_case
396
397 # Strip the '_' prefix
398 while name.has_prefix("_") do name = name.substring(1, name.length-1)
399
400 # Escape Nit keywords
401 if nit_keywords.has(name) then name += "_"
402
403 # If the name starts by something other than a letter, prefix with `java_`
404 if not name.chars.first.is_letter then name = "java_" + name
405
406 name = name.replace("$", "_")
407
408 return name
409 end
410 end
411
412 redef class JavaClass
413 # Property names used in this class
414 private var used_names = new HashSet[String]
415
416 # Get an available property name for the Java property with `name` and parameters
417 #
418 # If `use_parameters_name` then expect that there will be conflicts,
419 # so use the types of `parameters` to build the name.
420 private fun nit_name_for(name: String, parameters: Array[JavaType], use_parameters_name: Bool, is_static: Bool): String
421 do
422 # Append the name of each parameter
423 if use_parameters_name then
424 for param in parameters do
425 name += "_" + param.id
426 end
427 end
428
429 # Set of property names, local or top-level
430 var used_names
431 if is_static then
432 used_names = sys.top_level_used_names
433 else used_names = self.used_names
434
435 # As a last resort, append numbers to the name
436 var base_name = name
437 var count = 1
438 while used_names.has(name) do
439 name = base_name + count.to_s
440 count += 1
441 end
442
443 used_names.add name
444 return name
445 end
446 end