contrib/jwrapper: add some top-level methods to the reserved keywords
[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 then comment = "#"
155
156 var cast = jparam.param_cast
157
158 nit_types.add(nit_type)
159
160 if i == jparam_list.length - 1 then
161 java_params += "{cast}{nit_id}{nit_id_no}"
162 nit_params += "{nit_id}{nit_id_no}: {nit_type}"
163 else
164 java_params += "{cast}{nit_id}{nit_id_no}" + ", "
165 nit_params += "{nit_id}{nit_id_no}: {nit_type}, "
166 end
167
168 nit_id_no += 1
169 end
170
171 # Method documentation
172 var doc = "\t# Java implementation: {java_class}.{jmethod_id}\n"
173
174 # Method identifier
175 method_id = method_id.to_nit_method_name
176 method_id = java_class.nit_name_for(method_id, jparam_list, java_class.methods[jmethod_id].length > 1)
177 var nit_signature = new Array[String]
178
179 nit_signature.add "\tfun {method_id}"
180
181 if not jparam_list.is_empty then
182 nit_signature.add "({nit_params})"
183 end
184
185 var return_type = null
186 if not jreturn_type.is_void then
187 return_type = model.java_to_nit_type(jreturn_type)
188
189 if not return_type.is_known then comment = "#"
190
191 nit_signature.add ": {return_type} "
192 end
193
194 file_out.write doc
195 file_out.write comment + nit_signature.join
196
197 if comment == "#" then
198 file_out.write " in \"Java\" `\{\n{comment}\t\tself.{jmethod_id}({java_params});\n{comment}\t`\}\n"
199 # Methods with return type
200 else if return_type != null then
201 file_out.write " in \"Java\" `\{\n{comment}\t\treturn {jreturn_type.return_cast}self.{jmethod_id}({java_params});\n{comment}\t`\}\n"
202 # Methods without return type
203 else if jreturn_type.is_void then
204 file_out.write " in \"Java\" `\{\n{comment}\t\tself.{jmethod_id}({java_params});\n{comment}\t`\}\n"
205 # No copy
206 else
207 file_out.write " in \"Java\" `\{\n{comment}\t\tself.{jmethod_id}({java_params});\n{comment}\t`\}\n"
208 end
209 end
210
211 # Generate getter and setter to access an attribute, of field
212 private fun generate_getter_setter(java_class: JavaClass, java_id: String, java_type: JavaType)
213 do
214 var nit_type = model.java_to_nit_type(java_type)
215 var nit_id = java_id.to_nit_method_name
216 nit_id = java_class.nit_name_for(nit_id, [java_type], false)
217
218 var c = ""
219 if not nit_type.is_known then c = "#"
220
221 file_out.write """
222 # Java getter: {{{java_class}}}.{{{java_id}}}
223 {{{c}}} fun {{{nit_id}}}: {{{nit_type}}} in "Java" `{
224 {{{c}}} return self.{{{java_id}}};
225 {{{c}}} `}
226
227 # Java setter: {{{java_class}}}.{{{java_id}}}
228 {{{c}}} fun {{{nit_id}}}=(value: {{{nit_type}}}) in "Java" `{
229 {{{c}}} self.{{{java_id}}} = value;
230 {{{c}}} `}
231
232 """
233 end
234
235 # Generate getter and setter to access an attribute, of field
236 private fun generate_constructor(java_class: JavaClass, constructor: JavaConstructor, name: String)
237 do
238 var c = ""
239 var nit_params_s = ""
240 var java_params_s = ""
241
242 if constructor.params.not_empty then
243 var nit_params = new Array[String]
244 var java_params = new Array[String]
245 var param_id = 'a'
246 for java_type in constructor.params do
247
248 java_params.add "{java_type.param_cast}{param_id}"
249
250 var nit_type = model.java_to_nit_type(java_type)
251 nit_params.add "{param_id}: {nit_type}"
252 param_id = param_id.successor(1)
253
254 if not nit_type.is_known then c = "#"
255 end
256
257 nit_params_s = "(" + nit_params.join(", ") + ")"
258 java_params_s = java_params.join(", ")
259 end
260
261 file_out.write """
262 # Java constructor: {{{java_class}}}
263 {{{c}}} new {{{name}}}{{{nit_params_s}}} in "Java" `{
264 {{{c}}} return new {{{java_class}}}({{{java_params_s}}});
265 {{{c}}} `}
266
267 """
268 end
269 end
270
271 redef class Sys
272 # List of Nit keywords
273 #
274 # These may also be keywords in Java, but there they would be used capitalized.
275 private var nit_keywords = new HashSet[String].from(["abort", "abstract", "and", "assert",
276 "break", "class", "continue", "do", "else", "end", "enum", "extern", "false", "implies",
277 "import", "init", "interface", "intrude", "if", "in", "is", "isa", "isset", "for", "label",
278 "loop", "module", "new", "not", "null", "nullable", "or", "package", "private",
279 "protected", "public", "return", "self", "super", "then", "true", "type", "var", "while",
280
281 # Top-level methods
282 "class_name", "get_time", "hash", "is_same_type", "is_same_instance", "output",
283
284 # Pointer or JavaObject methods
285 "free"])
286 end
287
288 redef class String
289
290 # Convert the Java method name `self` to the Nit style
291 #
292 # * Converts to snake case
293 # * Strips `Get` and `Set`
294 # * Add suffix `=` to setters
295 fun to_nit_method_name: String
296 do
297 var name = self.to_snake_case
298 if name.has_prefix("get_") then
299 name = name.substring_from(4)
300 else if name.has_prefix("set_") then
301 name = name.substring_from(4)
302 if nit_keywords.has(name) then name += "_"
303 name += "="
304 end
305
306 # Strip the '_' prefix
307 while name.has_prefix("_") do name = name.substring(1, name.length-1)
308
309 # Escape Nit keywords
310 if nit_keywords.has(name) then name += "_"
311
312 # If the name starts by something other than a letter, prefix with `java_`
313 if not name.chars.first.is_letter then name = "java_" + name
314
315 name = name.replace("$", "_")
316
317 return name
318 end
319 end
320
321 redef class JavaClass
322 # Property names used in this class
323 private var used_name = new HashSet[String]
324
325 # Get an available property name for the Java property with `name` and parameters
326 #
327 # If `use_parameters_name` then expect that there will be conflicts,
328 # so use the types of `parameters` to build the name.
329 private fun nit_name_for(name: String, parameters: Array[JavaType], use_parameters_name: Bool): String
330 do
331 # Append the name of each parameter
332 if use_parameters_name then
333 for param in parameters do
334 name += "_" + param.id
335 end
336 end
337
338 # As a last resort, append numbers to the name
339 var base_name = name
340 var count = 1
341 while used_name.has(name) do
342 name = base_name + count.to_s
343 count += 1
344 end
345
346 used_name.add name
347 return name
348 end
349 end