contrib/jwrapper: do not skip primitive array types
[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
175 java_args.add "{jparam.param_cast}{nit_id}{nit_id_no}"
176 nit_params.add "{nit_id}{nit_id_no}: {nit_type}"
177
178 nit_id_no += 1
179 end
180
181 # Method identifier
182 method_id = method_id.to_nit_method_name
183 method_id = java_class.nit_name_for(method_id, java_params, java_class.methods[java_method_id].length > 1)
184
185 # Build the signature
186 var nit_signature = new Array[String]
187 nit_signature.add "fun {method_id}"
188 if not java_params.is_empty then nit_signature.add "({nit_params.join(", ")})"
189
190 # Return value
191 var return_type = null
192 if not java_return_type.is_void then
193 return_type = model.java_to_nit_type(java_return_type)
194
195 if not return_type.is_known and comment_unknown_types then c = "#"
196
197 nit_signature.add ": " + return_type.to_s
198 end
199
200 # Build the call in Java
201 var java_call
202 if is_static == true then
203 java_call = java_class.class_type.to_package_name
204 else java_call = "self"
205 java_call += ".{java_method_id}({java_args.join(", ")})"
206
207 if return_type != null then java_call = "return {java_return_type.return_cast}" + java_call
208
209 # Tabulation
210 var t = "\t"
211 if is_static == true then t = ""
212 var ct = c+t
213
214 # Write
215 file_out.write """
216 {{{t}}}# Java implementation: {{{java_return_type}}} {{{java_class}}}.{{{java_method_id}}}({{{java_params.join(", ")}}})
217 {{{ct}}}{{{nit_signature.join}}} in "Java" `{
218 {{{ct}}} {{{java_call}}};
219 {{{ct}}}`}
220 """
221 end
222
223 # Generate getter and setter to access an attribute, of field
224 private fun generate_getter_setter(java_class: JavaClass, java_id: String,
225 attribute: JavaAttribute)
226 do
227 var java_type = attribute.java_type
228 var nit_type = model.java_to_nit_type(java_type)
229
230 var nit_id = java_id
231 if attribute.is_static then nit_id = java_class.class_type.extern_name.to_snake_case + "_" + nit_id
232 nit_id = nit_id.to_nit_method_name
233 nit_id = java_class.nit_name_for(nit_id, [java_type], false)
234
235 var c = ""
236 if not nit_type.is_known and comment_unknown_types then c = "#"
237
238 var recv
239 if attribute.is_static == true then
240 recv = java_class.class_type.to_package_name
241 else recv = "self"
242
243 # Tabulation
244 var t = "\t"
245 if attribute.is_static then t = ""
246 var ct = c+t
247
248 file_out.write """
249 {{{t}}}# Java getter: {{{java_class}}}.{{{java_id}}}
250 {{{ct}}}fun {{{nit_id}}}: {{{nit_type}}} in "Java" `{
251 {{{ct}}} return {{{recv}}}.{{{java_id}}};
252 {{{ct}}}`}
253
254 {{{t}}}# Java setter: {{{java_class}}}.{{{java_id}}}
255 {{{ct}}}fun {{{nit_id}}}=(value: {{{nit_type}}}) in "Java" `{
256 {{{ct}}} {{{recv}}}.{{{java_id}}} = value;
257 {{{ct}}}`}
258
259 """
260 end
261
262 # Generate getter and setter to access an attribute, of field
263 private fun generate_constructor(java_class: JavaClass, constructor: JavaConstructor, name: String)
264 do
265 var c = ""
266 var nit_params_s = ""
267 var java_params_s = ""
268
269 if constructor.params.not_empty then
270 var nit_params = new Array[String]
271 var java_params = new Array[String]
272 var param_id = 'a'
273 for java_type in constructor.params do
274
275 java_params.add "{java_type.param_cast}{param_id}"
276
277 var nit_type = model.java_to_nit_type(java_type)
278 nit_params.add "{param_id}: {nit_type}"
279 param_id = param_id.successor(1)
280
281 if not nit_type.is_known and comment_unknown_types then c = "#"
282 end
283
284 nit_params_s = "(" + nit_params.join(", ") + ")"
285 java_params_s = java_params.join(", ")
286 end
287
288 file_out.write """
289 # Java constructor: {{{java_class}}}
290 {{{c}}} new {{{name}}}{{{nit_params_s}}} in "Java" `{
291 {{{c}}} return new {{{java_class}}}({{{java_params_s}}});
292 {{{c}}} `}
293
294 """
295 end
296
297 private fun generate_primitive_array(java_class: JavaClass, dimensions: Int)
298 do
299 var base_java_type = java_class.class_type
300 var java_type = base_java_type.clone
301 java_type.array_dimension = dimensions
302
303 var base_nit_type = model.java_to_nit_type(base_java_type)
304 var nit_type = model.java_to_nit_type(java_type)
305
306 file_out.write """
307 # Java primitive array: {{{java_type}}}
308 extern class {{{nit_type}}} in "Java" `{ {{{java_type.extern_equivalent}}} `}
309 super AbstractJavaArray[{{{base_nit_type}}}]
310
311 # Get a new array of the given `size`
312 new(size: Int) in "Java" `{ return new {{{base_java_type}}}[(int)size]; `}
313
314 redef fun [](i) in "Java" `{ return self[(int)i]; `}
315
316 redef fun []=(i, e) in "Java" `{ self[(int)i] = e; `}
317
318 redef fun length in "Java" `{ return self.length; `}
319 end
320
321 """
322 end
323 end
324
325 redef class Sys
326 # List of Nit keywords
327 #
328 # These may also be keywords in Java, but there they would be used capitalized.
329 private var nit_keywords = new HashSet[String].from(["abort", "abstract", "and", "assert",
330 "break", "class", "continue", "do", "else", "end", "enum", "extern", "false", "implies",
331 "import", "init", "interface", "intrude", "if", "in", "is", "isa", "isset", "for", "label",
332 "loop", "module", "new", "not", "null", "nullable", "or", "package", "private",
333 "protected", "public", "return", "self", "super", "then", "true", "type", "var", "while",
334
335 # Top-level methods
336 "class_name", "get_time", "hash", "is_same_type", "is_same_instance", "output",
337
338 # Pointer or JavaObject methods
339 "free"])
340 end
341
342 redef class String
343
344 # Convert the Java method name `self` to the Nit style
345 #
346 # * Converts to snake case
347 # * Strips `Get` and `Set`
348 # * Add suffix `=` to setters
349 fun to_nit_method_name: String
350 do
351 var name = self.to_snake_case
352
353 # Strip the '_' prefix
354 while name.has_prefix("_") do name = name.substring(1, name.length-1)
355
356 # Escape Nit keywords
357 if nit_keywords.has(name) then name += "_"
358
359 # If the name starts by something other than a letter, prefix with `java_`
360 if not name.chars.first.is_letter then name = "java_" + name
361
362 name = name.replace("$", "_")
363
364 return name
365 end
366 end
367
368 redef class JavaClass
369 # Property names used in this class
370 private var used_name = new HashSet[String]
371
372 # Get an available property name for the Java property with `name` and parameters
373 #
374 # If `use_parameters_name` then expect that there will be conflicts,
375 # so use the types of `parameters` to build the name.
376 private fun nit_name_for(name: String, parameters: Array[JavaType], use_parameters_name: Bool): String
377 do
378 # Append the name of each parameter
379 if use_parameters_name then
380 for param in parameters do
381 name += "_" + param.id
382 end
383 end
384
385 # As a last resort, append numbers to the name
386 var base_name = name
387 var count = 1
388 while used_name.has(name) do
389 name = base_name + count.to_s
390 count += 1
391 end
392
393 used_name.add name
394 return name
395 end
396 end