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