jwrapper: accept output files that do not look like Nit modules
[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 #
5 # Licensed under the Apache License, Version 2.0 (the "License");
6 # you may not use this file except in compliance with the License.
7 # You may obtain a copy of the License at
8 #
9 # http://www.apache.org/licenses/LICENSE-2.0
10 #
11 # Unless required by applicable law or agreed to in writing, software
12 # distributed under the License is distributed on an "AS IS" BASIS,
13 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 # See the License for the specific language governing permissions and
15 # limitations under the License.
16
17 # Services to generate extern class `in "Java"`
18 module code_generator
19
20 intrude import model
21
22 class CodeGenerator
23
24 var with_attributes: Bool
25 var comment_unknown_types: Bool
26 var file_out: OFStream
27 var java_class: JavaClass
28 var nb_params: Int
29 var module_name: nullable String = null
30
31 init (file_name: String, jclass: JavaClass, with_attributes, comment: Bool)
32 do
33 file_out = new OFStream.open(file_name)
34
35 var nit_ext = ".nit"
36 if file_name.has_suffix(nit_ext) then
37 # Output file ends with .nit, we expect it to be a valid name
38 module_name = file_name.strip_extension(nit_ext)
39
40 # Otherwise, it may be anything so do not declare a module
41 end
42
43 self.java_class = jclass
44 self.with_attributes = with_attributes
45 self.comment_unknown_types = comment
46 end
47
48 fun generate
49 do
50 var jclass = self.java_class
51
52 var class_content = new Array[String]
53 class_content.add(gen_class_header(jclass.class_type))
54
55 if with_attributes then
56 for id, jtype in jclass.attributes do class_content.add(gen_attribute(id, jtype))
57 end
58
59 for id, methods_info in jclass.methods do
60 for method_info in methods_info do
61 var nid = id
62 if methods_info.length > 1 then nid += "{methods_info.index_of(method_info)}"
63 class_content.add gen_method(id, nid, method_info.return_type, method_info.params)
64 end
65 end
66 class_content.add("\nend\n")
67
68 var wrappers = new Array[String]
69 for jtype in jclass.unknown_types do
70 if jtype == jclass.class_type then continue
71 wrappers.add("\n")
72 wrappers.add(gen_unknown_class_header(jtype))
73 end
74
75 var imports = new Array[String]
76 imports.add("import mnit_android\n")
77 for import_ in jclass.imports do
78 imports.add("import android::{import_}\n")
79 end
80
81 file_out.write(gen_licence)
82
83 var module_name = module_name
84 if module_name != null then file_out.write "module {module_name}\n"
85
86 file_out.write("\n")
87 file_out.write(imports.join(""))
88 file_out.write("\n")
89 file_out.write(class_content.join(""))
90 file_out.write(wrappers.join(""))
91 end
92
93 fun gen_licence: String
94 do
95 return """
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 """
112 end
113
114 fun gen_class_header(jtype: JavaType): String
115 do
116 var temp = new Array[String]
117 temp.add("extern class Native{jtype.id} in \"Java\" `\{ {jtype} `\}\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_attribute(jid: String, jtype: JavaType): String
140 do
141 return "\tvar {jid.to_nit_method_name}: {jtype.to_nit_type}\n"
142 end
143
144 fun gen_method(jmethod_id: String, nmethod_id: String, jreturn_type: JavaType, jparam_list: Array[JavaType]): String
145 do
146 var java_params = ""
147 var nit_params = ""
148 var nit_id = "arg"
149 var nit_id_no = 0
150 var nit_types = new Array[NitType]
151 var comment = ""
152
153 # Parameters
154 for i in [0..jparam_list.length[ do
155 var jparam = jparam_list[i]
156 var nit_type = jparam.to_nit_type
157
158 if not nit_type.is_complete then
159 if jparam.is_wrapped then
160 java_class.imports.add nit_type.mod.as(not null)
161 else
162 if comment_unknown_types then
163 comment = "#"
164 else
165 nit_type = jparam.extern_name
166 java_class.unknown_types.add(jparam)
167 end
168 end
169 end
170
171 var cast = ""
172
173 if not jparam.is_collection then cast = jparam.param_cast
174
175 nit_types.add(nit_type)
176 nit_type.arg_id = "{nit_id}{nit_id_no}"
177
178 if i == jparam_list.length - 1 then
179 java_params += "{cast}{nit_id}{nit_id_no}"
180 nit_params += "{nit_id}{nit_id_no}: {nit_type}"
181 else
182 java_params += "{cast}{nit_id}{nit_id_no}" + ", "
183 nit_params += "{nit_id}{nit_id_no}: {nit_type}, "
184 end
185
186 nit_id_no += 1
187 end
188
189 # Method identifier
190 var method_id = nmethod_id.to_nit_method_name
191 var nit_signature = new Array[String]
192
193 nit_signature.add "\tfun {method_id}"
194
195 if not jparam_list.is_empty then
196 nit_signature.add "({nit_params})"
197 end
198
199 var return_type = null
200
201 if not jreturn_type.is_void then
202 return_type = jreturn_type.to_nit_type
203
204 if not return_type.is_complete then
205 if jreturn_type.is_wrapped then
206 java_class.imports.add return_type.mod.as(not null)
207 else
208 if comment_unknown_types then
209 comment = "#"
210 else
211 return_type = jreturn_type.extern_name
212 java_class.unknown_types.add(jreturn_type)
213 end
214 end
215 end
216
217 nit_signature.add ": {return_type} "
218 end
219
220 var temp = new Array[String]
221
222 temp.add(comment + nit_signature.join(""))
223
224 # FIXME : This huge `if` block is only necessary to copy primitive arrays as long as there's no better way to do it
225 if comment == "#" then
226 temp.add(" in \"Java\" `\{\n{comment}\t\trecv.{jmethod_id}({java_params});\n{comment}\t`\}\n")
227 # Methods with return type
228 else if return_type != null then
229 temp.add(" in \"Java\" `\{\n{comment}\t\treturn {jreturn_type.return_cast}recv.{jmethod_id}({java_params});\n{comment}\t`\}\n")
230 # Methods without return type
231 else if jreturn_type.is_void then
232 temp.add(" in \"Java\" `\{\n{comment}\t\trecv.{jmethod_id}({java_params});\n{comment}\t`\}\n")
233 # No copy
234 else
235 temp.add(" in \"Java\" `\{\n{comment}\t\trecv.{jmethod_id}({java_params});\n{comment}\t`\}\n")
236 end
237
238 return temp.join("")
239 end
240 end
241
242 redef class String
243 # Convert the Java method name `self` to the Nit style
244 #
245 # * Converts to snake case
246 # * Strips `Get` and `Set`
247 # * Add suffix `=` to setters
248 fun to_nit_method_name: String
249 do
250 var name = self.to_snake_case
251 if name.has_prefix("get_") then
252 name = name.substring_from(4)
253 else if name.has_prefix("set_") then
254 name = name.substring_from(4) + "="
255 end
256
257 return name
258 end
259 end