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