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