contrib/jwrapper: accept [] (and anything) in search for existing wrappers
[nit.git] / contrib / jwrapper / src / model.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 # Contains the java and nit type representation used to convert java to nit code
19 module model
20
21 import more_collections
22 import opts
23
24 import jtype_converter
25
26 class JavaType
27 super Cloneable
28
29 var identifier = new Array[String]
30 var generic_params: nullable Array[JavaType] = null
31
32 # Is this a void return type?
33 var is_void = false
34
35 # Is this type a vararg?
36 var is_vararg = false is writable
37
38 # Has some generic type to be resolved (T extends foo => T is resolved to foo)
39 var has_unresolved_types = false
40
41 # Dimension of primitive array: `int[][]` is 2d
42 var array_dimension = 0
43
44 fun is_primitive_array: Bool do return array_dimension > 0
45
46 fun has_generic_params: Bool do return not generic_params == null
47 fun full_id: String do return identifier.join(".")
48 fun id: String do return identifier.last.replace("$", "")
49
50 fun return_cast: String do return converter.cast_as_return(self.id)
51
52 fun param_cast: String
53 do
54 if self.has_generic_params then
55 return converter.cast_as_param(self.generic_params[0].id)
56 end
57
58 return converter.cast_as_param(self.id)
59 end
60
61 # Name to give an extern class wrapping this type
62 fun extern_name: String
63 do
64 var name
65 var prefix = extern_class_prefix
66 if prefix == null then
67 # Use the namespace, e.g. java.lang.String -> Java_lang_String
68 assert not identifier.is_empty
69 if identifier.length == 1 then
70 name = identifier.last
71 else
72 var first = identifier.first
73 var last = identifier.last
74 var mid = identifier.subarray(1, identifier.length-2)
75 name = first.simple_capitalized + "_"
76 if mid.not_empty then name += mid.join("_") + "_"
77 name += last
78 end
79 else
80 # Use the prefix and the short class name
81 # e.g. given the prefix Native: java.lang.String -> NativeString
82 name = prefix + id
83 end
84
85 name = name.replace("-", "_")
86 name = name.replace("$", "_")
87 return name
88 end
89
90 redef fun to_s
91 do
92 var id = self.full_id
93
94 if self.is_primitive_array then
95 id += "[]" * array_dimension
96 else if self.has_generic_params then
97 var params = [for param in generic_params do param.to_s]
98 id += "<{params.join(", ")}>"
99 end
100
101 return id
102 end
103
104 # To fully qualified package name
105 # Cuts the primitive array `[]`
106 fun to_package_name: String
107 do
108 var str = self.to_s
109 var len = str.length
110
111 return str.substring(0, len - (2*array_dimension))
112 end
113
114 fun resolve_types(conversion_map: HashMap[String, Array[String]])
115 do
116 if identifier.length == 1 then
117 var resolved_id = conversion_map.get_or_null(self.id)
118 if resolved_id != null then self.identifier = new Array[String].from(resolved_id)
119 end
120
121 if self.has_generic_params then
122 for params in generic_params do params.resolve_types(conversion_map)
123 end
124 end
125
126 # Get a copy of `self`
127 redef fun clone
128 do
129 var jtype = new JavaType
130 jtype.identifier = identifier
131 jtype.generic_params = generic_params
132 jtype.is_void = is_void
133 jtype.is_vararg = is_vararg
134 jtype.array_dimension = array_dimension
135 return jtype
136 end
137
138 # Comparison based on fully qualified named
139 redef fun ==(other) do return other isa JavaType and
140 self.full_id == other.full_id and
141 self.is_primitive_array == other.is_primitive_array
142
143 redef fun hash do return self.full_id.hash
144 end
145
146 class NitType
147 # Nit class name
148 var identifier: String
149
150 # If this NitType was found in `lib/android`, contains the module name to import
151 var mod: nullable NitModule
152
153 # Is this type known, wrapped and available in Nit?
154 var is_known: Bool = true
155
156 redef fun to_s do return identifier
157 end
158
159 # Model of a single Java class
160 class JavaClass
161 # Type of this class
162 var class_type: JavaType
163
164 # Attributes of this class
165 var attributes = new HashMap[String, JavaAttribute]
166
167 # Methods of this class organized by their name
168 var methods = new MultiHashMap[String, JavaMethod]
169
170 # Constructors of this class
171 var constructors = new Array[JavaConstructor]
172
173 # Importations from this class
174 var imports = new HashSet[NitModule]
175
176 redef fun to_s do return class_type.to_s
177 end
178
179 # Model of all the Java class analyzed in one run
180 class JavaModel
181
182 # All analyzed classes
183 var classes = new HashMap[String, JavaClass]
184
185 # Add a class in `classes`
186 fun add_class(jclass: JavaClass)
187 do
188 var key = jclass.class_type.full_id
189 classes[key] = jclass
190 end
191
192 # Unknown types, not already wrapped and not in this pass
193 private var unknown_types = new HashMap[JavaType, NitType]
194
195 # Wrapped types, or classes analyzed in this pass
196 private var known_types = new HashMap[JavaType, NitType]
197
198 # Get the `NitType` corresponding to the `JavaType`
199 #
200 # Also registers types so they can be reused and
201 # to keep track of unknown types.
202 fun java_to_nit_type(jtype: JavaType): NitType
203 do
204 # Check cache
205 if known_types.keys.has(jtype) then return known_types[jtype]
206 if unknown_types.keys.has(jtype) then return unknown_types[jtype]
207
208 # Is it a compatible primitive type?
209 if not jtype.is_primitive_array then
210 var name = converter.to_nit_type(jtype.id)
211 if name != null then
212 # We got a Nit equivalent
213 var nit_type = new NitType(name)
214 known_types[jtype] = nit_type
215 return nit_type
216 end
217 end
218
219 # Is being wrapped in this pass?
220 var key = jtype.full_id
221 if classes.keys.has(key) then
222 var nit_type = new NitType(jtype.extern_name)
223 known_types[jtype] = nit_type
224
225 return nit_type
226 end
227
228 # Search in lib
229 var nit_type = find_extern_class[jtype.full_id]
230 if nit_type != null then
231 known_types[jtype] = nit_type
232 return nit_type
233 end
234
235 # Unknown type
236 nit_type = new NitType(jtype.extern_name)
237 nit_type.is_known = false
238 unknown_types[jtype] = nit_type
239 return nit_type
240 end
241 end
242
243 # A property to a Java class
244 abstract class JavaProperty
245
246 # Is this property marked static?
247 var is_static: Bool
248 end
249
250 # A Java method, with its signature
251 class JavaMethod
252 super JavaProperty
253
254 # Type returned by the method
255 var return_type: JavaType
256
257 # Type of the arguments of the method
258 var params: Array[JavaType]
259 end
260
261 # An attribute in a Java class
262 class JavaAttribute
263 super JavaProperty
264
265 # Type of the attribute
266 var java_type: JavaType
267 end
268
269 # A Java method, with its signature
270 class JavaConstructor
271 # Type of the parameters of this constructor
272 var params: Array[JavaType]
273 end
274
275 # A Nit module, use to import the referenced extern classes
276 class NitModule
277 # Relative path to the module
278 var path: String
279
280 # Name of the module
281 var name: String is lazy do return path.basename(".nit")
282
283 redef fun to_s do return self.name
284 redef fun ==(other) do return other isa NitModule and self.path == other.path
285 redef fun hash do return self.path.hash
286 end
287
288 redef class Sys
289 # Collection of Java classes already wrapped in the library
290 #
291 # * The key is from `JavaType.full_id`.
292 # * The value is the corresponding `NitType`.
293 var find_extern_class: DefaultMap[String, nullable NitType] is lazy do
294 var map = new DefaultMap[String, nullable NitType](null)
295 var modules = new HashMap[String, NitModule]
296
297 var lib_paths = opt_libs.value
298 if lib_paths == null then lib_paths = new Array[String]
299
300 if lib_paths.has("auto") then
301 lib_paths.remove "auto"
302 var nit_dir = "NIT_DIR".environ
303 if nit_dir.is_empty then
304 # Simple heuristic to find the Nit lib
305 var dir = sys.program_name.dirname / "../../../lib/"
306 dir = dir.simplify_path
307 if dir.file_exists then lib_paths.add dir.simplify_path
308 end
309 end
310
311 if lib_paths.is_empty then return map
312
313 # Use grep to find all extern classes implemented in Java
314 var grep_regex = "extern class [a-zA-Z0-9_]\\\+[ ]\\\+in[ ]\\\+\"Java\""
315 var grep_args = ["-r", "--with-filename", grep_regex]
316 grep_args.add_all lib_paths
317
318 var grep = new ProcessReader("grep", grep_args...)
319 var lines = grep.read_lines
320 grep.close
321 grep.wait
322
323 # Sort out the modules, Nit class names and Java types
324 var regex = """(.+):\\s*extern +class +([a-zA-Z0-9_]+) *in *"Java" *`\\{(.+)`\\}""".to_re
325 for line in lines do
326 var matches = line.search_all(regex)
327 for match in matches do
328 var path = match[1].to_s
329 var nit_name = match[2].to_s
330 var java_name = match[3].to_s.trim
331
332 # Debug code
333 # print "+ Found {nit_name}: {java_name} at {path}"
334
335 var mod = modules.get_or_null(path)
336 if mod == null then
337 mod = new NitModule(path)
338 modules[path] = mod
339 end
340
341 map[java_name] = new NitType(nit_name, mod)
342 end
343 end
344
345 return map
346 end
347
348 # Option to set `extern_class_prefix`
349 var opt_extern_class_prefix = new OptionString("Prefix to extern classes (By default uses the full namespace)", "-p")
350
351 # Prefix used to name extern classes, if `null` use the full namespace
352 var extern_class_prefix: nullable String is lazy do return opt_extern_class_prefix.value
353
354 # Libraries to search for existing wrappers
355 var opt_libs = new OptionArray("Paths to libraries with wrappers of Java classes ('auto' to use the full Nit lib)", "-i")
356 end
357
358 redef class Text
359 # Get a copy of `self` where the first letter is capitalized
360 fun simple_capitalized: String
361 do
362 if is_empty then return to_s
363
364 var c = chars.first.to_upper
365 var s = c.to_s + substring_from(1)
366 return s
367 end
368 end