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