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