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