6c52a620e99d77c22ee6f5a5ed62b615d8538dd8
[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 jinstanciation = create_array_instance(java_type, nit_type, jarray_id)
309 var return_str = ""
310
311 if has_return then
312 return_str = "return "
313 end
314
315 params_id = params_id.replace(nit_type.arg_id, jarray_id)
316
317 return """{{{imports}}} in "Java" `{
318 {{{jinstanciation}}}
319 int {{{narray_id}}} = new_{{{nit_type.id}}}_of_{{{nit_type.generic_params.join("_")}}}();
320
321 {{{loop_}}}
322
323 {{{return_str}}}recv.{{{jmethod_id}}}({{{params_id}}});
324 `}
325 """
326 end
327
328 # One collection parameter and the return type will be copied
329 fun param_return_copy(return_types, param_types: Couple[JavaType, NitType], jmethod_id, params_id: String): String
330 do
331 var narray_id = "nit_array"
332 var narray_id2 = "nit_array2"
333
334 var r_jtype = return_types.first
335 var r_ntype = return_types.second
336
337 var p_jtype = param_types.first
338 var p_ntype = param_types.second
339
340 var r_loop = create_loop(r_jtype, r_ntype, false, "java_array", narray_id)
341 var p_loop = create_loop(p_jtype, p_ntype, true, "java_array2", narray_id2)
342
343 var imports = new Array[String]
344
345 # Avoid import duplication
346 if p_ntype.to_s != r_ntype.to_s then
347 imports.add create_imports(p_ntype, true)
348 end
349
350 imports.add create_imports(r_ntype, false)
351
352 params_id = params_id.replace(p_ntype.arg_id, narray_id)
353
354 var jinstanciation = create_array_instance(p_jtype, p_ntype, "java_array")
355
356 return """{{{imports.join(", ")}}} in "Java" `{
357 {{{jinstanciation}}}
358
359 {{{p_loop}}}
360
361 {{{r_jtype.to_s}}} java_array2 = recv.{{{jmethod_id}}}({{{params_id}}});
362 int {{{narray_id2}}} = new_{{{r_ntype.id}}}_of_{{{r_ntype.generic_params.join("_")}}}();
363
364 {{{r_loop}}}
365
366 return {{{narray_id2}}};
367 `}
368 """
369 end
370
371 private fun create_array_instance(java_type: JavaType, nit_type: NitType, jarray_id: String): String
372 do
373 var jtype = java_type.to_s
374 var instanciation = ""
375
376 if java_type.is_primitive_array then
377 instanciation = "{jtype} {jarray_id} = new {java_type.full_id}[(int)Array_of_{nit_type.generic_params[0]}_length({nit_type.arg_id})];"
378 else
379 instanciation = "{jtype} {jarray_id} = new {jtype}();"
380 end
381
382 return instanciation
383 end
384
385 private fun create_imports(nit_type: NitType, is_param: Bool): String
386 do
387 var imports = ""
388 var ntype = nit_type.to_s
389 var gen_type = nit_type.generic_params.join(", ")
390
391 if not is_param then
392 if nit_type.is_map then
393 imports = """ import {{{ntype}}}, {{{ntype}}}.[]="""
394 else
395 imports = """ import {{{ntype}}}, {{{ntype}}}.add"""
396 end
397 else if nit_type.id == "Array" then
398 imports = """ import {{{ntype}}}, {{{ntype}}}.length, {{{ntype}}}.[]"""
399 else if nit_type.is_map then
400 imports = """ import {{{ntype}}}.iterator, Iterator[{{{gen_type}}}].is_ok, Iterator[{{{gen_type}}}].next, Iterator[{{{gen_type}}}].item, Iterator[{{{gen_type}}}].key"""
401 else
402 imports = """ import {{{ntype}}}.iterator, Iterator[{{{gen_type}}}].is_ok, Iterator[{{{gen_type}}}].next, Iterator[{{{gen_type}}}].item"""
403 end
404
405 return imports
406 end
407
408 private fun create_loop(java_type: JavaType, nit_type: NitType, is_param: Bool, jarray_id, narray_id: String): String
409 do
410 var loop_header = ""
411 var loop_body = ""
412 var gen_type = nit_type.generic_params.join("_")
413
414 if is_param then
415 if java_type.is_primitive_array then
416 loop_header = "for(int i=0; i < {jarray_id}.length; ++i)"
417 loop_body = """\t\t\t{{{jarray_id}}}[i] = {{{java_type.param_cast}}}Array_of_{{{gen_type}}}__index({{{nit_type.arg_id}}}, i);"""
418 else if nit_type.id == "Array" then
419 loop_header = """int length = Array_of_{{{gen_type}}}_length((int){{{nit_type.arg_id}}});\n\t\tfor(int i=0; i < length; ++i)"""
420 loop_body = """\t\t\t{{{jarray_id}}}.add({{{java_type.param_cast}}}Array_of_{{{gen_type}}}__index({{{narray_id}}}, i));"""
421 else
422 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)) {"""
423 if nit_type.is_map then
424 var key_cast = java_type.to_cast(java_type.generic_params[0].id, true)
425 var value_cast = java_type.to_cast(java_type.generic_params[1].id, true)
426 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}"""
427 else
428 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}"""
429 end
430 end
431 else
432 if nit_type.is_map then
433 var key_cast = java_type.to_cast(java_type.generic_params[0].id, false)
434 var value_cast = java_type.to_cast(java_type.generic_params[1].id, false)
435 loop_header = """for (java.util.Map.Entry<{{{java_type.generic_params[0]}}}, {{{java_type.generic_params[1]}}}> e: {{{jarray_id}}})"""
436 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());"""
437 else if java_type.is_iterable then
438 loop_header = """for ({{{java_type.generic_params[0]}}} e: {{{jarray_id}}})"""
439 loop_body = """\t\t\t{{{nit_type.id}}}_of_{{{gen_type}}}_add({{{narray_id}}}, {{{java_type.return_cast}}}e);"""
440 else
441 loop_header = "for(int i=0; i < {jarray_id}.length; ++i)"
442 loop_body = """\t\t\t{{{nit_type.id}}}_of_{{{gen_type}}}_add({{{narray_id}}}, {{{java_type.return_cast}}}{{{jarray_id}}}[i]);"""
443 end
444 end
445
446 return loop_header + "\n" + loop_body
447 end
448 end