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