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