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