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