neo_doxygen: Quiet warning about the old-style `init`.
[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 #
5 # Licensed under the Apache License, Version 2.0 (the "License");
6 # you may not use this file except in compliance with the License.
7 # You may obtain a copy of the License at
8 #
9 # http://www.apache.org/licenses/LICENSE-2.0
10 #
11 # Unless required by applicable law or agreed to in writing, software
12 # distributed under the License is distributed on an "AS IS" BASIS,
13 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 # See the License for the specific language governing permissions and
15 # limitations under the License.
16
17 # Services to generate extern class `in "Java"`
18 module code_generator
19
20 intrude import types
21
22 class CodeGenerator
23
24 var with_attributes: Bool
25 var comment_unknown_types: Bool
26 var file_out: OFStream
27 var java_class: JavaClass
28 var nb_params: Int
29 var module_name: String
30 fun code_warehouse: CodeWarehouse do return once new CodeWarehouse
31
32 init (file_name: String, jclass: JavaClass, with_attributes, comment: Bool)
33 do
34 file_out = new OFStream.open(file_name)
35 module_name = file_name.substring(0, file_name.search(".nit").from)
36 self.java_class = jclass
37 self.with_attributes = with_attributes
38 self.comment_unknown_types = comment
39 end
40
41 fun generate
42 do
43 var jclass = self.java_class
44
45 var class_content = new Array[String]
46 class_content.add(gen_class_header(jclass.class_type))
47
48 if with_attributes then
49 for id, jtype in jclass.attributes do class_content.add(gen_attribute(id, jtype))
50 end
51
52 for id, methods_info in jclass.methods do
53 for method_info in methods_info do
54 var nid = id
55 if methods_info.length > 1 then nid += "{methods_info.index_of(method_info)}"
56 class_content.add gen_method(id, nid, method_info.return_type, method_info.params)
57 end
58 end
59 class_content.add("\nend\n")
60
61 var wrappers = new Array[String]
62 for jtype in jclass.unknown_types do
63 if jtype == jclass.class_type then continue
64 wrappers.add("\n")
65 wrappers.add(gen_unknown_class_header(jtype))
66 end
67
68 var imports = new Array[String]
69 imports.add("import mnit_android\n")
70 for import_ in jclass.imports do
71 imports.add("import android::{import_}\n")
72 end
73
74 file_out.write(gen_licence)
75 file_out.write("module {module_name}\n")
76 file_out.write(imports.join(""))
77 file_out.write("\n")
78 file_out.write(class_content.join(""))
79 file_out.write(wrappers.join(""))
80 end
81
82 fun gen_licence: String
83 do
84 return """# This file is part of NIT (http://www.nitlanguage.org).
85 #
86 # Copyright [Year] [Author name] <Author e-mail>
87 #
88 # Licensed under the Apache License, Version 2.0 (the "License");
89 # you may not use this file except in compliance with the License.
90 # You may obtain a copy of the License at
91 #
92 # http://www.apache.org/licenses/LICENSE-2.0
93 #
94 # Unless required by applicable law or agreed to in writing, software
95 # distributed under the License is distributed on an "AS IS" BASIS,
96 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
97 # See the License for the specific language governing permissions and
98 # limitations under the License.
99
100 # This code has been generated using `javap`
101 """
102 end
103
104 fun gen_class_header(jtype: JavaType): String
105 do
106 var temp = new Array[String]
107 temp.add("extern class Native{jtype.id} in \"Java\" `\{ {jtype} `\}\n")
108 temp.add("\tsuper JavaObject\n\tredef type SELF: Native{jtype.id}\n\n")
109
110 return temp.join("")
111 end
112
113 fun gen_unknown_class_header(jtype: JavaType): String
114 do
115 var nit_type: NitType
116 if jtype.extern_name.has_generic_params then
117 nit_type = jtype.extern_name.generic_params.first
118 else
119 nit_type = jtype.extern_name
120 end
121
122 var temp = new Array[String]
123 temp.add("extern class {nit_type} in \"Java\" `\{ {jtype.to_package_name} `\}\n")
124 temp.add("\tsuper JavaObject\n\tredef type SELF: {nit_type}\n\nend\n")
125
126 return temp.join("")
127 end
128
129 fun gen_attribute(jid: String, jtype: JavaType): String
130 do
131 return "\tvar {jid.to_snake_case}: {jtype.to_nit_type}\n"
132 end
133
134 fun gen_method(jmethod_id: String, nmethod_id: String, jreturn_type: JavaType, jparam_list: Array[JavaType]): String
135 do
136 var java_params = ""
137 var nit_params = ""
138 var nit_id = "arg"
139 var nit_id_no = 0
140 var nit_types = new Array[NitType]
141 var comment = ""
142
143 # Parameters
144 for i in [0..jparam_list.length[ do
145 var jparam = jparam_list[i]
146 var nit_type = jparam.to_nit_type
147
148 if not nit_type.is_complete then
149 if jparam.is_wrapped then
150 java_class.imports.add nit_type.mod.as(not null)
151 else
152 if comment_unknown_types then
153 comment = "#"
154 else
155 nit_type = jparam.extern_name
156 java_class.unknown_types.add(jparam)
157 end
158 end
159 end
160
161 var cast = ""
162
163 if not jparam.is_collection then cast = jparam.param_cast
164
165 nit_types.add(nit_type)
166 nit_type.arg_id = "{nit_id}{nit_id_no}"
167
168 if i == jparam_list.length - 1 then
169 java_params += "{cast}{nit_id}{nit_id_no}"
170 nit_params += "{nit_id}{nit_id_no}: {nit_type}"
171 else
172 java_params += "{cast}{nit_id}{nit_id_no}" + ", "
173 nit_params += "{nit_id}{nit_id_no}: {nit_type}, "
174 end
175
176 nit_id_no += 1
177 end
178
179 # Method identifier
180 var method_id = nmethod_id.to_snake_case
181 var nit_signature = new Array[String]
182
183 nit_signature.add "\tfun {method_id}"
184
185 if not jparam_list.is_empty then
186 nit_signature.add "({nit_params})"
187 end
188
189 var return_type = null
190
191 if not jreturn_type.is_void then
192 return_type = jreturn_type.to_nit_type
193
194 if not return_type.is_complete then
195 if jreturn_type.is_wrapped then
196 java_class.imports.add return_type.mod.as(not null)
197 else
198 if comment_unknown_types then
199 comment = "#"
200 else
201 return_type = jreturn_type.extern_name
202 java_class.unknown_types.add(jreturn_type)
203 end
204 end
205 end
206
207 nit_signature.add ": {return_type} "
208 end
209
210 var param_to_copy = param_to_copy(jparam_list, nit_types)
211
212 var temp = new Array[String]
213
214 if nb_params > 1 then
215 comment = "#"
216 temp.add("\t# NOT SUPPORTED: more than one parameter to copy\n")
217 temp.add("\t# Has to be implemented manually\n")
218 end
219
220 temp.add(comment + nit_signature.join(""))
221
222 # FIXME : This huge `if` block is only necessary to copy primitive arrays as long as there's no better way to do it
223 if comment == "#" then
224 temp.add(" in \"Java\" `\{\n{comment}\t\trecv.{jmethod_id}({java_params}); \n{comment}\t`\}\n")
225 # Methods with return type
226 else if return_type != null then
227 if jreturn_type.is_primitive_array then
228 # Copy one parameter and the return value
229 if param_to_copy != null then
230 var rtype_couple = new Couple[JavaType, NitType](jreturn_type, return_type)
231 temp.add(code_warehouse.param_return_copy(rtype_couple, param_to_copy, jmethod_id, java_params))
232 # Copy the return type
233 else
234 temp.add(code_warehouse.return_type_copy(jreturn_type, return_type, jmethod_id, java_params))
235 end
236 # Copy the parameter
237 else if param_to_copy != null then
238 temp.add(code_warehouse.param_type_copy(param_to_copy.first, param_to_copy.second, jmethod_id, java_params, true))
239 # No copy
240 else
241 temp.add(" in \"Java\" `\{\n{comment}\t\treturn {jreturn_type.return_cast} recv.{jmethod_id}({java_params}); \n{comment}\t`\}\n")
242 end
243 # Methods without return type
244 else if jreturn_type.is_void then
245 # Copy one parameter
246 if param_to_copy != null then
247 temp.add(code_warehouse.param_type_copy(param_to_copy.first, param_to_copy.second, jmethod_id, java_params, false))
248 # No copy
249 else
250 temp.add(" in \"Java\" `\{\n{comment}\t\trecv.{jmethod_id}({java_params}); \n{comment}\t`\}\n")
251 end
252 # No copy
253 else
254 temp.add(" in \"Java\" `\{\n{comment}\t\trecv.{jmethod_id}({java_params}); \n{comment}\t`\}\n")
255 end
256
257 return temp.join("")
258 end
259
260 # Only one primitive array parameter can be copied
261 # If there's none or more than one then `null` is returned
262 fun param_to_copy(jtypes: Array[JavaType], ntypes: Array[NitType]): nullable Couple[JavaType, NitType]
263 do
264 var counter = 0
265 var couple = null
266 for i in [0..jtypes.length[ do
267 if jtypes[i].is_primitive_array then
268 counter += 1
269 couple = new Couple[JavaType, NitType](jtypes[i], ntypes[i])
270 end
271 end
272
273 nb_params = counter
274
275 if counter > 1 then return null
276 return couple
277 end
278 end
279
280 # Contains raw code mostly used to copy collections
281 class CodeWarehouse
282
283 # Collection as return value
284 fun return_type_copy(java_type: JavaType, nit_type: NitType, jmethod_id, params_id: String): String
285 do
286 var narray_id = "nit_array"
287 var loop_ = create_loop(java_type, nit_type, false, "java_array", narray_id)
288 var imports = create_imports(nit_type, false)
289
290 return """{{{imports}}} in "Java" `{
291 {{{java_type.to_s}}} java_array = recv.{{{jmethod_id}}}({{{params_id}}});
292 int {{{narray_id}}} = new_{{{nit_type.id}}}_of_{{{nit_type.generic_params.join("_")}}}();
293
294 {{{loop_}}}
295
296 return {{{narray_id}}};
297 `}
298 """
299 end
300
301 # Collection as parameter
302 fun param_type_copy(java_type: JavaType, nit_type: NitType, jmethod_id, params_id: String, has_return: Bool): String
303 do
304 var narray_id = "nit_array"
305 var jarray_id = "java_array"
306 var loop_ = create_loop(java_type, nit_type, true, jarray_id, narray_id)
307 var imports = create_imports(nit_type, true)
308 var jtype = java_type.to_s
309 var jinstanciation = create_array_instance(java_type, nit_type, jarray_id)
310 var return_str = ""
311
312 if has_return then
313 return_str = "return "
314 end
315
316 params_id = params_id.replace(nit_type.arg_id, jarray_id)
317
318 return """{{{imports}}} in "Java" `{
319 {{{jinstanciation}}}
320 int {{{narray_id}}} = new_{{{nit_type.id}}}_of_{{{nit_type.generic_params.join("_")}}}();
321
322 {{{loop_}}}
323
324 {{{return_str}}}recv.{{{jmethod_id}}}({{{params_id}}});
325 `}
326 """
327 end
328
329 # One collection parameter and the return type will be copied
330 fun param_return_copy(return_types, param_types: Couple[JavaType, NitType], jmethod_id, params_id: String): String
331 do
332 var narray_id = "nit_array"
333 var narray_id2 = "nit_array2"
334
335 var r_jtype = return_types.first
336 var r_ntype = return_types.second
337
338 var p_jtype = param_types.first
339 var p_ntype = param_types.second
340
341 var r_loop = create_loop(r_jtype, r_ntype, false, "java_array", narray_id)
342 var p_loop = create_loop(p_jtype, p_ntype, true, "java_array2", narray_id2)
343
344 var imports = new Array[String]
345
346 # Avoid import duplication
347 if p_ntype.to_s != r_ntype.to_s then
348 imports.add create_imports(p_ntype, true)
349 end
350
351 imports.add create_imports(r_ntype, false)
352
353 params_id = params_id.replace(p_ntype.arg_id, narray_id)
354
355 var jinstanciation = create_array_instance(p_jtype, p_ntype, "java_array")
356
357 return """{{{imports.join(", ")}}} in "Java" `{
358 {{{jinstanciation}}}
359
360 {{{p_loop}}}
361
362 {{{r_jtype.to_s}}} java_array2 = recv.{{{jmethod_id}}}({{{params_id}}});
363 int {{{narray_id2}}} = new_{{{r_ntype.id}}}_of_{{{r_ntype.generic_params.join("_")}}}();
364
365 {{{r_loop}}}
366
367 return {{{narray_id2}}};
368 `}
369 """
370 end
371
372 private fun create_array_instance(java_type: JavaType, nit_type: NitType, jarray_id: String): String
373 do
374 var jtype = java_type.to_s
375 var instanciation = ""
376
377 if java_type.is_primitive_array then
378 instanciation = "{jtype} {jarray_id} = new {java_type.full_id}[(int)Array_of_{nit_type.generic_params[0]}_length({nit_type.arg_id})];"
379 else
380 instanciation = "{jtype} {jarray_id} = new {jtype}();"
381 end
382
383 return instanciation
384 end
385
386 private fun create_imports(nit_type: NitType, is_param: Bool): String
387 do
388 var imports = ""
389 var ntype = nit_type.to_s
390 var gen_type = nit_type.generic_params.join(", ")
391
392 if not is_param then
393 if nit_type.is_map then
394 imports = """ import {{{ntype}}}, {{{ntype}}}.[]="""
395 else
396 imports = """ import {{{ntype}}}, {{{ntype}}}.add"""
397 end
398 else if nit_type.id == "Array" then
399 imports = """ import {{{ntype}}}, {{{ntype}}}.length, {{{ntype}}}.[]"""
400 else if nit_type.is_map then
401 imports = """ import {{{ntype}}}.iterator, Iterator[{{{gen_type}}}].is_ok, Iterator[{{{gen_type}}}].next, Iterator[{{{gen_type}}}].item, Iterator[{{{gen_type}}}].key"""
402 else
403 imports = """ import {{{ntype}}}.iterator, Iterator[{{{gen_type}}}].is_ok, Iterator[{{{gen_type}}}].next, Iterator[{{{gen_type}}}].item"""
404 end
405
406 return imports
407 end
408
409 private fun create_loop(java_type: JavaType, nit_type: NitType, is_param: Bool, jarray_id, narray_id: String): String
410 do
411 var loop_header = ""
412 var loop_body = ""
413 var gen_type = nit_type.generic_params.join("_")
414
415 if is_param then
416 if java_type.is_primitive_array then
417 loop_header = "for(int i=0; i < {jarray_id}.length; ++i)"
418 loop_body = """\t\t\t{{{jarray_id}}}[i] = {{{java_type.param_cast}}}Array_of_{{{gen_type}}}__index({{{nit_type.arg_id}}}, i);"""
419 else if nit_type.id == "Array" then
420 loop_header = """int length = Array_of_{{{gen_type}}}_length((int){{{nit_type.arg_id}}});\n\t\tfor(int i=0; i < length; ++i)"""
421 loop_body = """\t\t\t{{{jarray_id}}}.add({{{java_type.param_cast}}}Array_of_{{{gen_type}}}__index({{{narray_id}}}, i));"""
422 else
423 loop_header = """int itr = {{{nit_type.id}}}_of_{{{gen_type}}}_iterator({{{nit_type.arg_id}}});\n\t\twhile(Iterator_of_{{{gen_type}}}_is_ok(itr)) {"""
424 if nit_type.is_map then
425 var key_cast = java_type.to_cast(java_type.generic_params[0].id, true)
426 var value_cast = java_type.to_cast(java_type.generic_params[1].id, true)
427 loop_body = """\t\t\t{{{jarray_id}}}[{{{key_cast}}}iterator_of_{{{nit_type.id}}}_key(itr)] = {{{value_cast}}}iterator_of_{{{nit_type.id}}}_item(itr);\n\t\t\titerator_of_{{{gen_type}}}_next(itr);\n\t\t}"""
428 else
429 loop_body = """\t\t\t{{{jarray_id}}}.add({{{java_type.param_cast}}}iterator_of_{{{nit_type.id}}}_item(itr));\n\t\t\titerator_of_{{{gen_type}}}_next(itr);\n\t\t}"""
430 end
431 end
432 else
433 if nit_type.is_map then
434 var key_cast = java_type.to_cast(java_type.generic_params[0].id, false)
435 var value_cast = java_type.to_cast(java_type.generic_params[1].id, false)
436 loop_header = """for (java.util.Map.Entry<{{{java_type.generic_params[0]}}}, {{{java_type.generic_params[1]}}}> e: {{{jarray_id}}})"""
437 loop_body = """\t\t\t{{{nit_type.id}}}_of_{{{gen_type}}}_{{{nit_type.generic_params[1]}}}__index_assign({{{narray_id}}}, {{{key_cast}}}e.getKey(), {{{value_cast}}}e.getValue());"""
438 else if java_type.is_iterable then
439 loop_header = """for ({{{java_type.generic_params[0]}}} e: {{{jarray_id}}})"""
440 loop_body = """\t\t\t{{{nit_type.id}}}_of_{{{gen_type}}}_add({{{narray_id}}}, {{{java_type.return_cast}}}e);"""
441 else
442 loop_header = "for(int i=0; i < {jarray_id}.length; ++i)"
443 loop_body = """\t\t\t{{{nit_type.id}}}_of_{{{gen_type}}}_add({{{narray_id}}}, {{{java_type.return_cast}}}{{{jarray_id}}}[i]);"""
444 end
445 end
446
447 return loop_header + "\n" + loop_body
448 end
449 end