684db9c70d47158d5e064a483490eb285fa0f2de
[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 generate_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 generate_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 generate_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 private fun generate_class_header(jtype: JavaType)
129 do
130 var nit_type = jtype.to_nit_type
131 file_out.write "# Java class: {jtype.to_package_name}\n"
132 file_out.write "extern class {nit_type} in \"Java\" `\{ {jtype.to_package_name} `\}\n"
133 file_out.write "\tsuper JavaObject\n\n"
134 end
135
136 private fun generate_unknown_class_header(jtype: JavaType)
137 do
138 var nit_type = jtype.extern_name
139
140 file_out.write "extern class {nit_type} in \"Java\" `\{ {jtype.to_package_name} `\}\n"
141 file_out.write "\tsuper JavaObject\n\nend\n"
142 end
143
144 private fun generate_method(java_class: JavaClass, jmethod_id, method_id: String,
145 jreturn_type: JavaType, jparam_list: Array[JavaType])
146 do
147 var java_params = ""
148 var nit_params = ""
149 var nit_id = "arg"
150 var nit_id_no = 0
151 var nit_types = new Array[NitType]
152 var comment = ""
153
154 # Parameters
155 for i in [0..jparam_list.length[ do
156 var jparam = jparam_list[i]
157 var nit_type = jparam.to_nit_type
158
159 if not nit_type.is_complete then
160 if jparam.is_wrapped then
161 java_class.imports.add nit_type.mod.as(not null)
162 else
163 model.unknown_types.add jparam
164 if comment_unknown_types then
165 comment = "#"
166 else
167 nit_type = jparam.extern_name
168 end
169 end
170 end
171
172 var cast = ""
173
174 if not jparam.is_collection then cast = jparam.param_cast
175
176 nit_types.add(nit_type)
177
178 if i == jparam_list.length - 1 then
179 java_params += "{cast}{nit_id}{nit_id_no}"
180 nit_params += "{nit_id}{nit_id_no}: {nit_type}"
181 else
182 java_params += "{cast}{nit_id}{nit_id_no}" + ", "
183 nit_params += "{nit_id}{nit_id_no}: {nit_type}, "
184 end
185
186 nit_id_no += 1
187 end
188
189 # Method documentation
190 var doc = "\t# Java implementation: {java_class}.{jmethod_id}\n"
191
192 # Method identifier
193 method_id = method_id.to_nit_method_name
194 method_id = java_class.nit_name_for(method_id, jparam_list, java_class.methods[jmethod_id].length > 1)
195 var nit_signature = new Array[String]
196
197 nit_signature.add "\tfun {method_id}"
198
199 if not jparam_list.is_empty then
200 nit_signature.add "({nit_params})"
201 end
202
203 var return_type = null
204
205 if not jreturn_type.is_void then
206 return_type = jreturn_type.to_nit_type
207
208 if not return_type.is_complete then
209 if jreturn_type.is_wrapped then
210 java_class.imports.add return_type.mod.as(not null)
211 else
212 model.unknown_types.add jreturn_type
213 if comment_unknown_types then
214 comment = "#"
215 else
216 return_type = jreturn_type.extern_name
217 end
218 end
219 end
220
221 nit_signature.add ": {return_type} "
222 end
223
224 file_out.write doc
225 file_out.write comment + nit_signature.join
226
227 if comment == "#" then
228 file_out.write " in \"Java\" `\{\n{comment}\t\tself.{jmethod_id}({java_params});\n{comment}\t`\}\n"
229 # Methods with return type
230 else if return_type != null then
231 file_out.write " in \"Java\" `\{\n{comment}\t\treturn {jreturn_type.return_cast}self.{jmethod_id}({java_params});\n{comment}\t`\}\n"
232 # Methods without return type
233 else if jreturn_type.is_void then
234 file_out.write " in \"Java\" `\{\n{comment}\t\tself.{jmethod_id}({java_params});\n{comment}\t`\}\n"
235 # No copy
236 else
237 file_out.write " in \"Java\" `\{\n{comment}\t\tself.{jmethod_id}({java_params});\n{comment}\t`\}\n"
238 end
239 end
240
241 # Generate getter and setter to access an attribute, of field
242 private fun generate_getter_setter(java_class: JavaClass, java_id: String, java_type: JavaType)
243 do
244 var nit_type = model.java_to_nit_type(java_type)
245 var nit_id = java_id.to_nit_method_name
246 nit_id = java_class.nit_name_for(nit_id, [java_type], false)
247
248 var c = ""
249 if not nit_type.is_known then c = "#"
250
251 file_out.write """
252 # Java getter: {{{java_class}}}.{{{java_id}}}
253 {{{c}}} fun {{{nit_id}}}: {{{nit_type}}} in "Java" `{
254 {{{c}}} return self.{{{java_id}}};
255 {{{c}}} `}
256
257 # Java setter: {{{java_class}}}.{{{java_id}}}
258 {{{c}}} fun {{{nit_id}}}=(value: {{{nit_type}}}) in "Java" `{
259 {{{c}}} self.{{{java_id}}} = value;
260 {{{c}}} `}
261
262 """
263 end
264
265 # Generate getter and setter to access an attribute, of field
266 private fun generate_constructor(java_class: JavaClass, constructor: JavaConstructor, name: String)
267 do
268 var c = ""
269 var nit_params_s = ""
270 var java_params_s = ""
271
272 if constructor.params.not_empty then
273 var nit_params = new Array[String]
274 var java_params = new Array[String]
275 var param_id = 'a'
276 for java_type in constructor.params do
277
278 java_params.add "{java_type.param_cast}{param_id}"
279
280 var nit_type = model.java_to_nit_type(java_type)
281 nit_params.add "{param_id}: {nit_type}"
282 param_id = param_id.successor(1)
283
284 if not nit_type.is_known then c = "#"
285 end
286
287 nit_params_s = "(" + nit_params.join(", ") + ")"
288 java_params_s = java_params.join(", ")
289 end
290
291 file_out.write """
292 # Java constructor: {{{java_class}}}
293 {{{c}}} new {{{name}}}{{{nit_params_s}}} in "Java" `{
294 {{{c}}} return new {{{java_class}}}({{{java_params_s}}});
295 {{{c}}} `}
296
297 """
298 end
299 end
300
301 redef class Sys
302 # List of Nit keywords
303 #
304 # These may also be keywords in Java, but there they would be used capitalized.
305 private var nit_keywords: Array[String] = ["abort", "abstract", "and", "assert",
306 "break", "class", "continue", "do", "else", "end", "enum", "extern", "false", "implies",
307 "import", "init", "interface", "intrude", "if", "in", "is", "isa", "isset", "for", "label",
308 "loop", "module", "new", "not", "null", "nullable", "or", "package", "private",
309 "protected", "public", "return", "self", "super", "then", "true", "type", "var", "while"]
310 end
311
312 redef class String
313
314 # Convert the Java method name `self` to the Nit style
315 #
316 # * Converts to snake case
317 # * Strips `Get` and `Set`
318 # * Add suffix `=` to setters
319 fun to_nit_method_name: String
320 do
321 var name = self.to_snake_case
322 if name.has_prefix("get_") then
323 name = name.substring_from(4)
324 else if name.has_prefix("set_") then
325 name = name.substring_from(4)
326 if nit_keywords.has(name) then name += "_"
327 name += "="
328 end
329
330 # Strip the '_' prefix
331 while name.has_prefix("_") do name = name.substring(1, name.length-1)
332
333 # Escape Nit keywords
334 if nit_keywords.has(name) then name += "_"
335
336 # If the name starts by something other than a letter, prefix with `java_`
337 if not name.chars.first.is_letter then name = "java_" + name
338
339 name = name.replace("$", "_")
340
341 return name
342 end
343 end
344
345 redef class JavaClass
346 # Property names used in this class
347 private var used_name = new HashSet[String]
348
349 # Get an available property name for the Java property with `name` and parameters
350 #
351 # If `use_parameters_name` then expect that there will be conflicts,
352 # so use the types of `parameters` to build the name.
353 private fun nit_name_for(name: String, parameters: Array[JavaType], use_parameters_name: Bool): String
354 do
355 # Append the name of each parameter
356 if use_parameters_name then
357 for param in parameters do
358 name += "_" + param.id
359 end
360 end
361
362 # As a last resort, append numbers to the name
363 var base_name = name
364 var count = 1
365 while used_name.has(name) do
366 name = base_name + count.to_s
367 count += 1
368 end
369
370 used_name.add name
371 return name
372 end
373 end