contrib/jwrapper: generate doc above extern methods
[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: NitType
126 if jtype.extern_name.has_generic_params then
127 nit_type = jtype.extern_name.generic_params.first
128 else
129 nit_type = jtype.extern_name
130 end
131
132 var temp = new Array[String]
133 temp.add("extern class {nit_type} in \"Java\" `\{ {jtype.to_package_name} `\}\n")
134 temp.add("\tsuper JavaObject\n\nend\n")
135
136 return temp.join
137 end
138
139 fun gen_method(java_class: JavaClass, jmethod_id, nmethod_id: String, jreturn_type: JavaType, jparam_list: Array[JavaType]): String
140 do
141 var java_params = ""
142 var nit_params = ""
143 var nit_id = "arg"
144 var nit_id_no = 0
145 var nit_types = new Array[NitType]
146 var comment = ""
147
148 # Parameters
149 for i in [0..jparam_list.length[ do
150 var jparam = jparam_list[i]
151 var nit_type = jparam.to_nit_type
152
153 if not nit_type.is_complete then
154 if jparam.is_wrapped then
155 java_class.imports.add nit_type.mod.as(not null)
156 else
157 model.unknown_types.add jparam
158 if comment_unknown_types then
159 comment = "#"
160 else
161 nit_type = jparam.extern_name
162 end
163 end
164 end
165
166 var cast = ""
167
168 if not jparam.is_collection then cast = jparam.param_cast
169
170 nit_types.add(nit_type)
171 nit_type.arg_id = "{nit_id}{nit_id_no}"
172
173 if i == jparam_list.length - 1 then
174 java_params += "{cast}{nit_id}{nit_id_no}"
175 nit_params += "{nit_id}{nit_id_no}: {nit_type}"
176 else
177 java_params += "{cast}{nit_id}{nit_id_no}" + ", "
178 nit_params += "{nit_id}{nit_id_no}: {nit_type}, "
179 end
180
181 nit_id_no += 1
182 end
183
184 # Method documentation
185 var doc = "\t# Java implementation: {java_class}.{jmethod_id}\n"
186
187 # Method identifier
188 var method_id = nmethod_id.to_nit_method_name
189 var nit_signature = new Array[String]
190
191 nit_signature.add "\tfun {method_id}"
192
193 if not jparam_list.is_empty then
194 nit_signature.add "({nit_params})"
195 end
196
197 var return_type = null
198
199 if not jreturn_type.is_void then
200 return_type = jreturn_type.to_nit_type
201
202 if not return_type.is_complete then
203 if jreturn_type.is_wrapped then
204 java_class.imports.add return_type.mod.as(not null)
205 else
206 model.unknown_types.add jreturn_type
207 if comment_unknown_types then
208 comment = "#"
209 else
210 return_type = jreturn_type.extern_name
211 end
212 end
213 end
214
215 nit_signature.add ": {return_type} "
216 end
217
218 var temp = new Array[String]
219
220 temp.add doc
221 temp.add(comment + nit_signature.join)
222
223 if comment == "#" then
224 temp.add(" in \"Java\" `\{\n{comment}\t\tself.{jmethod_id}({java_params});\n{comment}\t`\}\n")
225 # Methods with return type
226 else if return_type != null then
227 temp.add(" in \"Java\" `\{\n{comment}\t\treturn {jreturn_type.return_cast}self.{jmethod_id}({java_params});\n{comment}\t`\}\n")
228 # Methods without return type
229 else if jreturn_type.is_void then
230 temp.add(" in \"Java\" `\{\n{comment}\t\tself.{jmethod_id}({java_params});\n{comment}\t`\}\n")
231 # No copy
232 else
233 temp.add(" in \"Java\" `\{\n{comment}\t\tself.{jmethod_id}({java_params});\n{comment}\t`\}\n")
234 end
235
236 return temp.join
237 end
238 end
239
240 redef class Sys
241 # List of Nit keywords
242 #
243 # These may also be keywords in Java, but there they would be used capitalized.
244 private var nit_keywords: Array[String] = ["abort", "abstract", "and", "assert",
245 "break", "class", "continue", "do", "else", "end", "enum", "extern", "implies",
246 "import", "init", "interface", "intrude", "if", "in", "is", "isa", "for", "label",
247 "loop", "module", "new", "not", "null", "nullable", "or", "package", "private",
248 "protected", "public", "return", "self", "super", "then", "type", "var", "while"]
249 end
250
251 redef class String
252
253 # Convert the Java method name `self` to the Nit style
254 #
255 # * Converts to snake case
256 # * Strips `Get` and `Set`
257 # * Add suffix `=` to setters
258 fun to_nit_method_name: String
259 do
260 var name = self.to_snake_case
261 if name.has_prefix("get_") then
262 name = name.substring_from(4)
263 else if name.has_prefix("set_") then
264 name = name.substring_from(4)
265 if nit_keywords.has(name) then name += "_"
266 name += "="
267 end
268
269 # Strip the '_' prefix
270 while name.has_prefix("_") do name = name.substring(1, name.length-1)
271
272 # Escape Nit keywords
273 if nit_keywords.has(name) then name += "_"
274
275 # If the name starts by something other than a letter, prefix with `java_`
276 if not name.chars.first.is_letter then name = "java_" + name
277
278 name = name.replace("$", "_")
279
280 return name
281 end
282 end