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"`
27 # Path to the output file
30 # Model of Java class being wrapped
33 # Comment out methods with unknown (unwrapped) types
34 var comment_unknown_types
: Bool
36 # Generate stub classes for unknown types used in the generated module
37 var stub_for_unknown_types
: Bool
40 var file_out
: Writer = new FileWriter.open
(file_name
) is lazy
, writable
42 # Name of the Nit module to generate
43 var module_name
: nullable String is lazy
do
44 if file_name
.file_extension
== "nit" then
45 # Output file ends with .nit, we expect it to be a valid name
46 return file_name
.basename
(".nit")
50 # Generate the Nit module into `file_out`
54 file_out
.write license
57 var module_name
= module_name
58 if module_name
!= null then file_out
.write
"module {module_name} is no_warning(\"useless-superclass\
")\n"
62 var imports
= new HashSet[String]
63 imports
.add
"import java\n"
64 for key
, jclass
in model
.classes
do
65 for import_
in jclass
.imports
do imports
.add
"import android::{import_}\n"
67 file_out
.write imports
.join
("\n")
70 # Sort classes from top-level classes (java.lang.Object) to leaves
71 var standard_classes
= new Array[JavaClass]
72 for name
, jclass
in model
.classes
do
73 if not jclass
.class_type
.is_anonymous
then standard_classes
.add jclass
75 var linearized
= model
.class_hierarchy
.linearize
(standard_classes
)
77 for jclass
in linearized
do
78 # Skip classes with an invalid name at the Java language level
79 if jclass
.class_type
.extern_equivalent
.has
("-") then continue
81 generate_class_header
(jclass
)
83 if not sys
.opt_no_properties
.value
then
85 for id
, signatures
in jclass
.local_intro_methods
do
86 for signature
in signatures
do
87 assert not signature
.is_static
88 generate_method
(jclass
, id
, id
, signature
.return_type
, signature
.params
)
94 for constructor
in jclass
.constructors
do
95 var complex
= jclass
.constructors
.length
!= 1 and constructor
.params
.not_empty
96 var base_name
= if complex
then "from" else ""
97 var name
= jclass
.nit_name_for
(base_name
, constructor
.params
, complex
, false, local_only
=true)
99 generate_constructor
(jclass
, constructor
, name
)
103 for id
, attribute
in jclass
.attributes
do if not attribute
.is_static
then
104 generate_getter_setter
(jclass
, id
, attribute
)
109 generate_jni_services jclass
.class_type
112 file_out
.write
"end\n\n"
114 if not sys
.opt_no_properties
.value
then
116 # Static functions as top-level methods
117 var static_functions_prefix
= jclass
.class_type
.extern_name
.to_snake_case
118 for id
, signatures
in jclass
.methods
do
119 for signature
in signatures
do if signature
.is_static
then
120 var nit_id
= static_functions_prefix
+ "_" + id
121 generate_method
(jclass
, id
, nit_id
, signature
.return_type
, signature
.params
, is_static
=true)
126 # Static attributes as top-level getters and setters
127 for id
, attribute
in jclass
.attributes
do if attribute
.is_static
then
128 generate_getter_setter
(jclass
, id
, attribute
)
133 for d
in [1..opt_arrays
.value
] do
134 generate_primitive_array
(jclass
, d
)
138 if stub_for_unknown_types
then
139 for jtype
, nit_type
in model
.unknown_types
do
140 generate_unknown_class_header
(jtype
)
148 # Serialize `model` to a file next to `file_name`
149 fun write_model_to_file
151 if not sys
.opt_save_model
.value
then return
153 # Write the model to file next to the Nit module
154 var model_path
= file_name
.strip_extension
+ ".jwrapper.bin"
155 var model_stream
= model_path
.to_path
.open_wo
156 var serializer
= new MsgPackSerializer(model_stream
)
157 serializer
.serialize model
161 # License for the header of the generated Nit module
163 # This file is part of NIT (http://www.nitlanguage.org).
165 # Licensed under the Apache License, Version 2.0 (the "License");
166 # you may not use this file except in compliance with the License.
167 # You may obtain a copy of the License at
169 # http://www.apache.org/licenses/LICENSE-2.0
171 # Unless required by applicable law or agreed to in writing, software
172 # distributed under the License is distributed on an "AS IS" BASIS,
173 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
174 # See the License for the specific language governing permissions and
175 # limitations under the License.
177 # This code has been generated using `jwrapper`
180 private fun generate_class_header
(java_class
: JavaClass)
182 var java_type
= java_class
.class_type
183 var nit_type
= model
.java_to_nit_type
(java_type
)
185 var super_java_types
= new HashSet[JavaType]
186 super_java_types
.add_all java_class
.extends
187 super_java_types
.add_all java_class
.implements
189 var supers
= new Array[String]
190 var effective_supers
= 0
191 for java_super
in super_java_types
do
192 var nit_super
= model
.java_to_nit_type
(java_super
)
194 # Comment out unknown types
196 if not nit_super
.is_known
and comment_unknown_types
then c
= "# "
198 supers
.add
"{c}super {nit_super}"
199 if c
!= "# " then effective_supers
+= 1
202 if effective_supers
== 0 then
203 if java_class
.class_type
.java_full_name
== "java.lang.Object" or
204 not model
.knows_the_object_class
then
205 supers
.add
"super JavaObject"
206 else supers
.add
"super Java_lang_Object"
210 # Java class: {{{java_type.extern_equivalent}}}
211 extern class {{{nit_type}}} in "Java" `{ {{{java_type.extern_equivalent}}} `}
212 {{{supers.join("\n\t")}}}
217 private fun generate_unknown_class_header
(jtype
: JavaType)
219 var nit_type
= jtype
.extern_name
221 file_out
.write
"extern class {nit_type} in \"Java\
" `\{ {jtype.extern_equivalent} `\}\n"
222 file_out
.write
"\tsuper JavaObject\n\nend\n"
225 private fun generate_method
(java_class
: JavaClass, java_method_id
, method_id
: String,
226 java_return_type
: JavaType, java_params
: Array[JavaType], is_static
: nullable Bool)
228 var java_args
= new Array[String]
229 var nit_params
= new Array[String]
235 for jparam
in java_params
do
236 var nit_type
= model
.java_to_nit_type
(jparam
)
238 if not nit_type
.is_known
and comment_unknown_types
then c
= "#"
239 if jparam
.is_vararg
then c
= "#"
241 java_args
.add
"{jparam.param_cast}{nit_id}{nit_id_no}"
242 nit_params
.add
"{nit_id}{nit_id_no}: {nit_type}"
248 method_id
= method_id
.to_nit_method_name
249 method_id
= java_class
.nit_name_for
(method_id
, java_params
, java_class
.methods
[java_method_id
].length
> 1, is_static
== true)
251 # Build the signature
252 var nit_signature
= new Array[String]
253 nit_signature
.add
"fun {method_id}"
254 if not java_params
.is_empty
then nit_signature
.add
"({nit_params.join(", ")})"
257 var return_type
= null
258 if not java_return_type
.is_void
then
259 return_type
= model
.java_to_nit_type
(java_return_type
)
261 if not return_type
.is_known
and comment_unknown_types
then c
= "#"
262 if java_return_type
.is_vararg
then c
= "#"
264 nit_signature
.add
": " + return_type
.to_s
267 # Build the call in Java
269 if is_static
== true then
270 java_call
= java_class
.class_type
.java_full_name
271 else java_call
= "self"
272 java_call
+= ".{java_method_id}({java_args.join(", ")})"
274 if return_type
!= null then java_call
= "return {java_return_type.return_cast}" + java_call
278 if is_static
== true then t
= ""
283 {{{t}}}# Java implementation: {{{java_return_type}}} {{{java_class.class_type.extern_equivalent}}}.{{{java_method_id}}}({{{java_params.join(", ")}}})
284 {{{ct}}}{{{nit_signature.join}}} in "Java" `{
285 {{{ct}}} {{{java_call}}};
290 # Generate getter and setter to access an attribute, of field
291 private fun generate_getter_setter
(java_class
: JavaClass, java_id
: String,
292 attribute
: JavaAttribute)
294 var java_type
= attribute
.java_type
295 var nit_type
= model
.java_to_nit_type
(java_type
)
298 if attribute
.is_static
then nit_id
= java_class
.class_type
.extern_name
.to_snake_case
+ "_" + nit_id
299 nit_id
= nit_id
.to_nit_method_name
300 nit_id
= java_class
.nit_name_for
(nit_id
, [java_type
], false, attribute
.is_static
)
303 if not nit_type
.is_known
and comment_unknown_types
then c
= "#"
304 if java_type
.is_vararg
then c
= "#"
307 if attribute
.is_static
then
308 recv
= java_class
.class_type
.java_full_name
313 if attribute
.is_static
then t
= ""
317 {{{t}}}# Java getter: {{{java_class.class_type.extern_equivalent}}}.{{{java_id}}}
318 {{{ct}}}fun {{{nit_id}}}: {{{nit_type}}} in "Java" `{
319 {{{ct}}} return {{{recv}}}.{{{java_id}}};
322 {{{t}}}# Java setter: {{{java_class.class_type.extern_equivalent}}}.{{{java_id}}}
323 {{{ct}}}fun {{{nit_id}}}=(value: {{{nit_type}}}) in "Java" `{
324 {{{ct}}} {{{recv}}}.{{{java_id}}} = value;
330 # Generate getter and setter to access an attribute, of field
331 private fun generate_constructor
(java_class
: JavaClass, constructor
: JavaConstructor, name
: String)
334 var nit_params_s
= ""
335 var java_params_s
= ""
337 if constructor
.params
.not_empty
then
338 var nit_params
= new Array[String]
339 var java_params
= new Array[String]
341 for java_type
in constructor
.params
do
343 java_params
.add
"{java_type.param_cast}{param_id}"
345 var nit_type
= model
.java_to_nit_type
(java_type
)
346 nit_params
.add
"{param_id}: {nit_type}"
347 param_id
= param_id
.successor
(1)
349 if not nit_type
.is_known
and comment_unknown_types
then c
= "#"
350 if java_type
.is_vararg
then c
= "#"
353 nit_params_s
= "(" + nit_params
.join
(", ") + ")"
354 java_params_s
= java_params
.join
(", ")
358 # Java constructor: {{{java_class.class_type.extern_equivalent}}}
359 {{{c}}} new {{{name}}}{{{nit_params_s}}} in "Java" `{
360 {{{c}}} return new {{{java_class.class_type.java_full_name}}}({{{java_params_s}}});
366 private fun generate_primitive_array
(java_class
: JavaClass, dimensions
: Int)
368 var base_java_type
= java_class
.class_type
369 var java_type
= base_java_type
.clone
370 java_type
.array_dimension
= dimensions
372 var base_nit_type
= model
.java_to_nit_type
(base_java_type
)
373 var nit_type
= model
.java_to_nit_type
(java_type
)
376 # Java primitive array: {{{java_type.extern_equivalent}}}
377 extern class {{{nit_type}}} in "Java" `{ {{{java_type.extern_equivalent}}} `}
378 super AbstractJavaArray[{{{base_nit_type}}}]
380 # Get a new array of the given `size`
381 new(size: Int) in "Java" `{ return new {{{base_java_type}}}[(int)size]; `}
383 redef fun [](i) in "Java" `{ return self[(int)i]; `}
385 redef fun []=(i, e) in "Java" `{ self[(int)i] = e; `}
387 redef fun length in "Java" `{ return self.length; `}
390 generate_jni_services
(java_type
)
397 # Generate JNI related services
399 # For now, mostly avoid issue #845, but more services could be generated as needed.
400 private fun generate_jni_services
(java_type
: JavaType)
402 var nit_type
= model
.java_to_nit_type
(java_type
)
405 redef fun new_global_ref import sys, Sys.jni_env `{
406 Sys sys = {{{nit_type}}}_sys(self);
407 JNIEnv *env = Sys_jni_env(sys);
408 return (*env)->NewGlobalRef(env, self);
411 redef fun pop_from_local_frame_with_env(jni_env) `{
412 return (*jni_env)->PopLocalFrame(jni_env, self);
419 # List of Nit keywords
421 # These may also be keywords in Java, but there they would be used capitalized.
422 private var nit_keywords
: Set[String] is lazy
do
423 var set
= new HashSet[String]
425 set
.add_all methods_in_pointer
429 # Name of methods used at the top-level
431 # Used by `JavaClass::nit_name_for` with static properties.
432 private var top_level_used_names
= new HashSet[String]
434 # Option to _not_ generate properties (static or from classes)
435 var opt_no_properties
= new OptionBool("Do not wrap properties, only classes and basic services", "-n", "--no-properties")
437 # Should the model be serialized to a file?
438 var opt_save_model
= new OptionBool("Save the model next to the generated Nit module", "-s", "--save-model")
443 # Convert the Java method name `self` to the Nit style
445 # * Converts to snake case
446 # * Strips `Get` and `Set`
447 # * Add suffix `=` to setters
448 fun to_nit_method_name
: String
450 var name
= self.to_snake_case
452 # Strip the '_' prefix
453 while name
.has_prefix
("_") do name
= name
.substring
(1, name
.length-1
)
455 # Escape Nit keywords
456 if nit_keywords
.has
(name
) then name
+= "_"
458 # If the name starts by something other than a letter, prefix with `java_`
459 if not name
.chars
.first
.is_letter
then name
= "java_" + name
461 name
= name
.replace
("$", "_")
467 redef class JavaClass
468 # Property names used in this class
469 private var used_names
= new HashSet[String] is serialize
471 # Get an available property name for the Java property with `name` and parameters
473 # If `use_parameters_name` then expect that there will be conflicts,
474 # so use the types of `parameters` to build the name.
475 private fun nit_name_for
(name
: String, parameters
: Array[JavaType], use_parameters_name
: Bool, is_static
: Bool, local_only
: nullable Bool): String
477 # Append the name of each parameter
478 if use_parameters_name
then
479 for param
in parameters
do
481 id
+= "Array"*param
.array_dimension
486 # Set of sets of property names, local or top-level
491 local_used_names
= sys
.top_level_used_names
492 used_names
= sys
.top_level_used_names
493 else if local_only
== true then
494 # Local only: constructors
495 local_used_names
= self.used_names
496 used_names
= self.used_names
498 # Avoid conflicts with all super classes
499 local_used_names
= self.used_names
500 used_names
= new HashSet[String]
501 for sup
in in_hierarchy
.greaters
do
502 used_names
.add_all sup
.used_names
506 # As a last resort, append numbers to the name
509 while used_names
.has
(name
) do
510 name
= base_name
+ count
.to_s
514 local_used_names
.add name