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