1 # This file is part of NIT (http://www.nitlanguage.org).
3 # Copyright 2014 Frédéric Vachon <fredvac@gmail.com>
4 # Copyright 2015 Alexis Laferrière <alexis.laf@xymus.net>
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
10 # http://www.apache.org/licenses/LICENSE-2.0
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.
18 # Services to generate extern class `in "Java"`
25 # Path to the output file
28 # Model of Java class being wrapped
31 # Comment out methods with unknown (unwrapped) types
32 var comment_unknown_types
: Bool
34 # Generate stub classes for unknown types used in the generated module
35 var stub_for_unknown_types
: Bool
38 var file_out
: Writer = new FileWriter.open
(file_name
) is lazy
, writable
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")
48 # Generate the Nit module into `file_out`
52 file_out
.write license
55 var module_name
= module_name
56 if module_name
!= null then file_out
.write
"module {module_name} is no_warning(\"useless-superclass\
")\n"
60 var imports
= new HashSet[String]
61 imports
.add
"import java\n"
62 for key
, jclass
in model
.classes
do
63 for import_
in jclass
.imports
do imports
.add
"import android::{import_}\n"
65 file_out
.write imports
.join
("\n")
68 # Sort classes from top-level classes (java.lang.Object) to leaves
69 var standard_classes
= new Array[JavaClass]
70 for name
, jclass
in model
.classes
do
71 if not jclass
.class_type
.is_anonymous
then standard_classes
.add jclass
73 var linearized
= model
.class_hierarchy
.linearize
(standard_classes
)
75 for jclass
in linearized
do
76 # Skip classes with an invalid name at the Java language level
77 if jclass
.class_type
.extern_equivalent
.has
("-") then continue
79 generate_class_header
(jclass
)
81 if not sys
.opt_no_properties
.value
then
83 for id
, signatures
in jclass
.local_intro_methods
do
84 for signature
in signatures
do
85 assert not signature
.is_static
86 generate_method
(jclass
, id
, id
, signature
.return_type
, signature
.params
)
92 for constructor
in jclass
.constructors
do
93 var complex
= jclass
.constructors
.length
!= 1 and constructor
.params
.not_empty
94 var base_name
= if complex
then "from" else ""
95 var name
= jclass
.nit_name_for
(base_name
, constructor
.params
, complex
, false, local_only
=true)
97 generate_constructor
(jclass
, constructor
, name
)
101 for id
, attribute
in jclass
.attributes
do if not attribute
.is_static
then
102 generate_getter_setter
(jclass
, id
, attribute
)
107 generate_jni_services jclass
.class_type
110 file_out
.write
"end\n\n"
112 if not sys
.opt_no_properties
.value
then
114 # Static functions as top-level methods
115 var static_functions_prefix
= jclass
.class_type
.extern_name
.to_snake_case
116 for id
, signatures
in jclass
.methods
do
117 for signature
in signatures
do if signature
.is_static
then
118 var nit_id
= static_functions_prefix
+ "_" + id
119 generate_method
(jclass
, id
, nit_id
, signature
.return_type
, signature
.params
, is_static
=true)
124 # Static attributes as top-level getters and setters
125 for id
, attribute
in jclass
.attributes
do if attribute
.is_static
then
126 generate_getter_setter
(jclass
, id
, attribute
)
131 for d
in [1..opt_arrays
.value
] do
132 generate_primitive_array
(jclass
, d
)
136 if stub_for_unknown_types
then
137 for jtype
, nit_type
in model
.unknown_types
do
138 generate_unknown_class_header
(jtype
)
146 # Serialize `model` to a file next to `file_name`
147 fun write_model_to_file
149 if not sys
.opt_save_model
.value
then return
151 # Write the model to file next to the Nit module
152 var model_path
= file_name
.strip_extension
+ ".jwrapper.bin"
153 var model_stream
= model_path
.to_path
.open_wo
154 var serializer
= new BinarySerializer(model_stream
)
155 serializer
.serialize model
159 # License for the header of the generated Nit module
161 # This file is part of NIT (http://www.nitlanguage.org).
163 # Licensed under the Apache License, Version 2.0 (the "License");
164 # you may not use this file except in compliance with the License.
165 # You may obtain a copy of the License at
167 # http://www.apache.org/licenses/LICENSE-2.0
169 # Unless required by applicable law or agreed to in writing, software
170 # distributed under the License is distributed on an "AS IS" BASIS,
171 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
172 # See the License for the specific language governing permissions and
173 # limitations under the License.
175 # This code has been generated using `jwrapper`
178 private fun generate_class_header
(java_class
: JavaClass)
180 var java_type
= java_class
.class_type
181 var nit_type
= model
.java_to_nit_type
(java_type
)
183 var super_java_types
= new HashSet[JavaType]
184 super_java_types
.add_all java_class
.extends
185 super_java_types
.add_all java_class
.implements
187 var supers
= new Array[String]
188 var effective_supers
= 0
189 for java_super
in super_java_types
do
190 var nit_super
= model
.java_to_nit_type
(java_super
)
192 # Comment out unknown types
194 if not nit_super
.is_known
and comment_unknown_types
then c
= "# "
196 supers
.add
"{c}super {nit_super}"
197 if c
!= "# " then effective_supers
+= 1
200 if effective_supers
== 0 then
201 if java_class
.class_type
.package_name
== "java.lang.Object" or
202 not model
.knows_the_object_class
then
203 supers
.add
"super JavaObject"
204 else supers
.add
"super Java_lang_Object"
208 # Java class: {{{java_type}}}
209 extern class {{{nit_type}}} in "Java" `{ {{{java_type.extern_equivalent}}} `}
210 {{{supers.join("\n\t")}}}
215 private fun generate_unknown_class_header
(jtype
: JavaType)
217 var nit_type
= jtype
.extern_name
219 file_out
.write
"extern class {nit_type} in \"Java\
" `\{ {jtype.extern_equivalent} `\}\n"
220 file_out
.write
"\tsuper JavaObject\n\nend\n"
223 private fun generate_method
(java_class
: JavaClass, java_method_id
, method_id
: String,
224 java_return_type
: JavaType, java_params
: Array[JavaType], is_static
: nullable Bool)
226 var java_args
= new Array[String]
227 var nit_params
= new Array[String]
233 for jparam
in java_params
do
234 var nit_type
= model
.java_to_nit_type
(jparam
)
236 if not nit_type
.is_known
and comment_unknown_types
then c
= "#"
237 if jparam
.is_vararg
then c
= "#"
239 java_args
.add
"{jparam.param_cast}{nit_id}{nit_id_no}"
240 nit_params
.add
"{nit_id}{nit_id_no}: {nit_type}"
246 method_id
= method_id
.to_nit_method_name
247 method_id
= java_class
.nit_name_for
(method_id
, java_params
, java_class
.methods
[java_method_id
].length
> 1, is_static
== true)
249 # Build the signature
250 var nit_signature
= new Array[String]
251 nit_signature
.add
"fun {method_id}"
252 if not java_params
.is_empty
then nit_signature
.add
"({nit_params.join(", ")})"
255 var return_type
= null
256 if not java_return_type
.is_void
then
257 return_type
= model
.java_to_nit_type
(java_return_type
)
259 if not return_type
.is_known
and comment_unknown_types
then c
= "#"
260 if java_return_type
.is_vararg
then c
= "#"
262 nit_signature
.add
": " + return_type
.to_s
265 # Build the call in Java
267 if is_static
== true then
268 java_call
= java_class
.class_type
.package_name
269 else java_call
= "self"
270 java_call
+= ".{java_method_id}({java_args.join(", ")})"
272 if return_type
!= null then java_call
= "return {java_return_type.return_cast}" + java_call
276 if is_static
== true then t
= ""
281 {{{t}}}# Java implementation: {{{java_return_type}}} {{{java_class}}}.{{{java_method_id}}}({{{java_params.join(", ")}}})
282 {{{ct}}}{{{nit_signature.join}}} in "Java" `{
283 {{{ct}}} {{{java_call}}};
288 # Generate getter and setter to access an attribute, of field
289 private fun generate_getter_setter
(java_class
: JavaClass, java_id
: String,
290 attribute
: JavaAttribute)
292 var java_type
= attribute
.java_type
293 var nit_type
= model
.java_to_nit_type
(java_type
)
296 if attribute
.is_static
then nit_id
= java_class
.class_type
.extern_name
.to_snake_case
+ "_" + nit_id
297 nit_id
= nit_id
.to_nit_method_name
298 nit_id
= java_class
.nit_name_for
(nit_id
, [java_type
], false, attribute
.is_static
)
301 if not nit_type
.is_known
and comment_unknown_types
then c
= "#"
302 if java_type
.is_vararg
then c
= "#"
305 if attribute
.is_static
then
306 recv
= java_class
.class_type
.package_name
311 if attribute
.is_static
then t
= ""
315 {{{t}}}# Java getter: {{{java_class}}}.{{{java_id}}}
316 {{{ct}}}fun {{{nit_id}}}: {{{nit_type}}} in "Java" `{
317 {{{ct}}} return {{{recv}}}.{{{java_id}}};
320 {{{t}}}# Java setter: {{{java_class}}}.{{{java_id}}}
321 {{{ct}}}fun {{{nit_id}}}=(value: {{{nit_type}}}) in "Java" `{
322 {{{ct}}} {{{recv}}}.{{{java_id}}} = value;
328 # Generate getter and setter to access an attribute, of field
329 private fun generate_constructor
(java_class
: JavaClass, constructor
: JavaConstructor, name
: String)
332 var nit_params_s
= ""
333 var java_params_s
= ""
335 if constructor
.params
.not_empty
then
336 var nit_params
= new Array[String]
337 var java_params
= new Array[String]
339 for java_type
in constructor
.params
do
341 java_params
.add
"{java_type.param_cast}{param_id}"
343 var nit_type
= model
.java_to_nit_type
(java_type
)
344 nit_params
.add
"{param_id}: {nit_type}"
345 param_id
= param_id
.successor
(1)
347 if not nit_type
.is_known
and comment_unknown_types
then c
= "#"
348 if java_type
.is_vararg
then c
= "#"
351 nit_params_s
= "(" + nit_params
.join
(", ") + ")"
352 java_params_s
= java_params
.join
(", ")
356 # Java constructor: {{{java_class}}}
357 {{{c}}} new {{{name}}}{{{nit_params_s}}} in "Java" `{
358 {{{c}}} return new {{{java_class.class_type.package_name}}}({{{java_params_s}}});
364 private fun generate_primitive_array
(java_class
: JavaClass, dimensions
: Int)
366 var base_java_type
= java_class
.class_type
367 var java_type
= base_java_type
.clone
368 java_type
.array_dimension
= dimensions
370 var base_nit_type
= model
.java_to_nit_type
(base_java_type
)
371 var nit_type
= model
.java_to_nit_type
(java_type
)
374 # Java primitive array: {{{java_type}}}
375 extern class {{{nit_type}}} in "Java" `{ {{{java_type.extern_equivalent}}} `}
376 super AbstractJavaArray[{{{base_nit_type}}}]
378 # Get a new array of the given `size`
379 new(size: Int) in "Java" `{ return new {{{base_java_type}}}[(int)size]; `}
381 redef fun [](i) in "Java" `{ return self[(int)i]; `}
383 redef fun []=(i, e) in "Java" `{ self[(int)i] = e; `}
385 redef fun length in "Java" `{ return self.length; `}
388 generate_jni_services
(java_type
)
395 # Generate JNI related services
397 # For now, mostly avoid issue #845, but more services could be generated as needed.
398 private fun generate_jni_services
(java_type
: JavaType)
400 var nit_type
= model
.java_to_nit_type
(java_type
)
403 redef fun new_global_ref import sys, Sys.jni_env `{
404 Sys sys = {{{nit_type}}}_sys(self);
405 JNIEnv *env = Sys_jni_env(sys);
406 return (*env)->NewGlobalRef(env, self);
409 redef fun pop_from_local_frame_with_env(jni_env) `{
410 return (*jni_env)->PopLocalFrame(jni_env, self);
417 # List of Nit keywords
419 # These may also be keywords in Java, but there they would be used capitalized.
420 private var nit_keywords
= new HashSet[String].from
(["abort", "abstract", "and", "assert",
421 "break", "class", "continue", "do", "else", "end", "enum", "extern", "false", "implies",
422 "import", "init", "interface", "intrude", "if", "in", "is", "isa", "isset", "for", "label",
423 "loop", "module", "new", "not", "null", "nullable", "or", "package", "private",
424 "protected", "public", "return", "self", "super", "then", "true", "type", "var", "while",
427 "class_name", "get_time", "hash", "inspect", "inspect_head", "is_same_type",
428 "is_same_instance", "object_id", "output", "output_class_name", "sys", "to_s",
430 # Pointer or JavaObject methods
433 # Name of methods used at the top-level
435 # Used by `JavaClass::nit_name_for` with static properties.
436 private var top_level_used_names
= new HashSet[String]
438 # Option to _not_ generate properties (static or from classes)
439 var opt_no_properties
= new OptionBool("Do not wrap properties, only classes and basic services", "-n", "--no-properties")
441 # Should the model be serialized to a file?
442 var opt_save_model
= new OptionBool("Save the model next to the generated Nit module", "-s", "--save-model")
447 # Convert the Java method name `self` to the Nit style
449 # * Converts to snake case
450 # * Strips `Get` and `Set`
451 # * Add suffix `=` to setters
452 fun to_nit_method_name
: String
454 var name
= self.to_snake_case
456 # Strip the '_' prefix
457 while name
.has_prefix
("_") do name
= name
.substring
(1, name
.length-1
)
459 # Escape Nit keywords
460 if nit_keywords
.has
(name
) then name
+= "_"
462 # If the name starts by something other than a letter, prefix with `java_`
463 if not name
.chars
.first
.is_letter
then name
= "java_" + name
465 name
= name
.replace
("$", "_")
471 redef class JavaClass
472 # Property names used in this class
473 private var used_names
= new HashSet[String] is serialize
475 # Get an available property name for the Java property with `name` and parameters
477 # If `use_parameters_name` then expect that there will be conflicts,
478 # so use the types of `parameters` to build the name.
479 private fun nit_name_for
(name
: String, parameters
: Array[JavaType], use_parameters_name
: Bool, is_static
: Bool, local_only
: nullable Bool): String
481 # Append the name of each parameter
482 if use_parameters_name
then
483 for param
in parameters
do
485 id
+= "Array"*param
.array_dimension
490 # Set of sets of property names, local or top-level
495 local_used_names
= sys
.top_level_used_names
496 used_names
= sys
.top_level_used_names
497 else if local_only
== true then
498 # Local only: constructors
499 local_used_names
= self.used_names
500 used_names
= self.used_names
502 # Avoid conflicts with all super classes
503 local_used_names
= self.used_names
504 used_names
= new HashSet[String]
505 for sup
in in_hierarchy
.greaters
do
506 used_names
.add_all sup
.used_names
510 # As a last resort, append numbers to the name
513 while used_names
.has
(name
) do
514 name
= base_name
+ count
.to_s
518 local_used_names
.add name