contrib/jwrapper: generate constructors
[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
70 file_out.write gen_class_header(jclass.class_type)
71
72 for id, signatures in jclass.methods do
73 var c = 0
74 for signature in signatures do
75 var nid = id
76 if c > 0 then nid += c.to_s
77 c += 1
78
79 file_out.write gen_method(jclass, id, nid, 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)
89
90 generate_constructor(jclass, constructor, name)
91 end
92
93 # Attributes
94 for id, java_type in jclass.attributes do
95 generate_getter_setter(jclass, id, java_type)
96 end
97
98 file_out.write "end\n\n"
99 end
100
101 if stub_for_unknown_types then
102 for jtype in model.unknown_types do
103 file_out.write gen_unknown_class_header(jtype)
104 file_out.write "\n"
105 end
106 end
107 end
108
109 # License for the header of the generated Nit module
110 var license = """
111 # This file is part of NIT (http://www.nitlanguage.org).
112 #
113 # Licensed under the Apache License, Version 2.0 (the "License");
114 # you may not use this file except in compliance with the License.
115 # You may obtain a copy of the License at
116 #
117 # http://www.apache.org/licenses/LICENSE-2.0
118 #
119 # Unless required by applicable law or agreed to in writing, software
120 # distributed under the License is distributed on an "AS IS" BASIS,
121 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
122 # See the License for the specific language governing permissions and
123 # limitations under the License.
124
125 # This code has been generated using `jwrapper`
126 """ is writable
127
128 fun gen_class_header(jtype: JavaType): String
129 do
130 var temp = new Array[String]
131 var nit_type = jtype.to_nit_type
132 temp.add "# Java class: {jtype.to_package_name}\n"
133 temp.add "extern class {nit_type} in \"Java\" `\{ {jtype.to_package_name} `\}\n"
134 temp.add "\tsuper JavaObject\n\n"
135
136 return temp.join
137 end
138
139 fun gen_unknown_class_header(jtype: JavaType): String
140 do
141 var nit_type = jtype.extern_name
142
143 var temp = new Array[String]
144 temp.add("extern class {nit_type} in \"Java\" `\{ {jtype.to_package_name} `\}\n")
145 temp.add("\tsuper JavaObject\n\nend\n")
146
147 return temp.join
148 end
149
150 fun gen_method(java_class: JavaClass, jmethod_id, nmethod_id: String, jreturn_type: JavaType, jparam_list: Array[JavaType]): String
151 do
152 var java_params = ""
153 var nit_params = ""
154 var nit_id = "arg"
155 var nit_id_no = 0
156 var nit_types = new Array[NitType]
157 var comment = ""
158
159 # Parameters
160 for i in [0..jparam_list.length[ do
161 var jparam = jparam_list[i]
162 var nit_type = jparam.to_nit_type
163
164 if not nit_type.is_complete then
165 if jparam.is_wrapped then
166 java_class.imports.add nit_type.mod.as(not null)
167 else
168 model.unknown_types.add jparam
169 if comment_unknown_types then
170 comment = "#"
171 else
172 nit_type = jparam.extern_name
173 end
174 end
175 end
176
177 var cast = ""
178
179 if not jparam.is_collection then cast = jparam.param_cast
180
181 nit_types.add(nit_type)
182
183 if i == jparam_list.length - 1 then
184 java_params += "{cast}{nit_id}{nit_id_no}"
185 nit_params += "{nit_id}{nit_id_no}: {nit_type}"
186 else
187 java_params += "{cast}{nit_id}{nit_id_no}" + ", "
188 nit_params += "{nit_id}{nit_id_no}: {nit_type}, "
189 end
190
191 nit_id_no += 1
192 end
193
194 # Method documentation
195 var doc = "\t# Java implementation: {java_class}.{jmethod_id}\n"
196
197 # Method identifier
198 var method_id = nmethod_id.to_nit_method_name
199 method_id = java_class.nit_name_for(method_id, jparam_list, java_class.methods[jmethod_id].length > 1)
200 var nit_signature = new Array[String]
201
202 nit_signature.add "\tfun {method_id}"
203
204 if not jparam_list.is_empty then
205 nit_signature.add "({nit_params})"
206 end
207
208 var return_type = null
209
210 if not jreturn_type.is_void then
211 return_type = jreturn_type.to_nit_type
212
213 if not return_type.is_complete then
214 if jreturn_type.is_wrapped then
215 java_class.imports.add return_type.mod.as(not null)
216 else
217 model.unknown_types.add jreturn_type
218 if comment_unknown_types then
219 comment = "#"
220 else
221 return_type = jreturn_type.extern_name
222 end
223 end
224 end
225
226 nit_signature.add ": {return_type} "
227 end
228
229 var temp = new Array[String]
230
231 temp.add doc
232 temp.add(comment + nit_signature.join)
233
234 if comment == "#" then
235 temp.add(" in \"Java\" `\{\n{comment}\t\tself.{jmethod_id}({java_params});\n{comment}\t`\}\n")
236 # Methods with return type
237 else if return_type != null then
238 temp.add(" in \"Java\" `\{\n{comment}\t\treturn {jreturn_type.return_cast}self.{jmethod_id}({java_params});\n{comment}\t`\}\n")
239 # Methods without return type
240 else if jreturn_type.is_void then
241 temp.add(" in \"Java\" `\{\n{comment}\t\tself.{jmethod_id}({java_params});\n{comment}\t`\}\n")
242 # No copy
243 else
244 temp.add(" in \"Java\" `\{\n{comment}\t\tself.{jmethod_id}({java_params});\n{comment}\t`\}\n")
245 end
246
247 return temp.join
248 end
249
250 # Generate getter and setter to access an attribute, of field
251 private fun generate_getter_setter(java_class: JavaClass, java_id: String, java_type: JavaType)
252 do
253 var nit_type = model.java_to_nit_type(java_type)
254 var nit_id = java_id.to_nit_method_name
255 nit_id = java_class.nit_name_for(nit_id, [java_type], false)
256
257 var c = ""
258 if not nit_type.is_known then c = "#"
259
260 file_out.write """
261 # Java getter: {{{java_class}}}.{{{java_id}}}
262 {{{c}}} fun {{{nit_id}}}: {{{nit_type}}} in "Java" `{
263 {{{c}}} return self.{{{java_id}}};
264 {{{c}}} `}
265
266 # Java setter: {{{java_class}}}.{{{java_id}}}
267 {{{c}}} fun {{{nit_id}}}=(value: {{{nit_type}}}) in "Java" `{
268 {{{c}}} self.{{{java_id}}} = value;
269 {{{c}}} `}
270
271 """
272 end
273
274 # Generate getter and setter to access an attribute, of field
275 private fun generate_constructor(java_class: JavaClass, constructor: JavaConstructor, name: String)
276 do
277 var c = ""
278 var nit_params_s = ""
279 var java_params_s = ""
280
281 if constructor.params.not_empty then
282 var nit_params = new Array[String]
283 var java_params = new Array[String]
284 var param_id = 'a'
285 for java_type in constructor.params do
286
287 java_params.add "{java_type.param_cast}{param_id}"
288
289 var nit_type = model.java_to_nit_type(java_type)
290 nit_params.add "{param_id}: {nit_type}"
291 param_id = param_id.successor(1)
292
293 if not nit_type.is_known then c = "#"
294 end
295
296 nit_params_s = "(" + nit_params.join(", ") + ")"
297 java_params_s = java_params.join(", ")
298 end
299
300 file_out.write """
301 # Java constructor: {{{java_class}}}
302 {{{c}}} new {{{name}}}{{{nit_params_s}}} in "Java" `{
303 {{{c}}} return new {{{java_class}}}({{{java_params_s}}});
304 {{{c}}} `}
305
306 """
307 end
308 end
309
310 redef class Sys
311 # List of Nit keywords
312 #
313 # These may also be keywords in Java, but there they would be used capitalized.
314 private var nit_keywords: Array[String] = ["abort", "abstract", "and", "assert",
315 "break", "class", "continue", "do", "else", "end", "enum", "extern", "false", "implies",
316 "import", "init", "interface", "intrude", "if", "in", "is", "isa", "isset", "for", "label",
317 "loop", "module", "new", "not", "null", "nullable", "or", "package", "private",
318 "protected", "public", "return", "self", "super", "then", "true", "type", "var", "while"]
319 end
320
321 redef class String
322
323 # Convert the Java method name `self` to the Nit style
324 #
325 # * Converts to snake case
326 # * Strips `Get` and `Set`
327 # * Add suffix `=` to setters
328 fun to_nit_method_name: String
329 do
330 var name = self.to_snake_case
331 if name.has_prefix("get_") then
332 name = name.substring_from(4)
333 else if name.has_prefix("set_") then
334 name = name.substring_from(4)
335 if nit_keywords.has(name) then name += "_"
336 name += "="
337 end
338
339 # Strip the '_' prefix
340 while name.has_prefix("_") do name = name.substring(1, name.length-1)
341
342 # Escape Nit keywords
343 if nit_keywords.has(name) then name += "_"
344
345 # If the name starts by something other than a letter, prefix with `java_`
346 if not name.chars.first.is_letter then name = "java_" + name
347
348 name = name.replace("$", "_")
349
350 return name
351 end
352 end
353
354 redef class JavaClass
355 # Property names used in this class
356 private var used_name = new HashSet[String]
357
358 # Get an available property name for the Java property with `name` and parameters
359 #
360 # If `use_parameters_name` then expect that there will be conflicts,
361 # so use the types of `parameters` to build the name.
362 private fun nit_name_for(name: String, parameters: Array[JavaType], use_parameters_name: Bool): String
363 do
364 # Append the name of each parameter
365 if use_parameters_name then
366 for param in parameters do
367 name += "_" + param.id
368 end
369 end
370
371 # As a last resort, append numbers to the name
372 var base_name = name
373 var count = 1
374 while used_name.has(name) do
375 name = base_name + count.to_s
376 count += 1
377 end
378
379 used_name.add name
380 return name
381 end
382 end