Merge: jwrapper supports static properties, primitive arrays and generic parameters
authorJean Privat <jean@pryen.org>
Thu, 30 Jul 2015 00:04:27 +0000 (20:04 -0400)
committerJean Privat <jean@pryen.org>
Thu, 30 Jul 2015 00:04:27 +0000 (20:04 -0400)
This PR continues the work of #1578 and #1589. It improves jwrapper to support more features of the Java language: static functions and attributes, primitives arrays and generic parameters. The resulting generated wrapper of large Java namespaces is now usable with semi-global compilation!

Main changes:

* Wrap static functions and attributes as top-level methods.

* Generate a primitive array wrapper for each target class. By default, only wrap 1 dimension arrays, it's enough in most cases (see stats at the bottom).

* Resolve generic parameters and use the closest bound (only the first bound).

* Add JNI services to each classes.

* Jwrapper is now good enough to wrap the `java` and `org` namespace of the Java library
  and to use them in semi-global compilation. See the new example `java_api` to see it in action.

  The _full_ Java generated by this tool and by the FFI pass the basic semantic check of javac,
  but it still raise errors and warnings on illegal access of internal, protected or obsolete entities.
  This may need manual tweaking of the tool to block these cases, that information is not made available by the output of javap.

  Compiling `java_api` in separate compilation takes about 20 minutes and crashes at the compilation of the generated C code. More investigation is required to find the problem, but I suspect the C file of 132 mo generated by the FFI to manipulate the JNI.

What's to do next:
* Class hierarchy!
* Negative filtering of the target classes in a Jar archive.
* Filtering of methods to avoid obsolete ones? Or simply remove them manually after generation?
* vararg support.
* Revamp the `java` module and low-level types.

## Some stats on the Android lib example

Disabled functions before this PR:
* java_api.nit: 1448 / 12805
* android_api.nit: 1075 / 22084

Disabled functions now:
* java_api.nit: 5 / 14248 # From a multi-dimensional array
* java_api.nit: 29 / 14248 # From a vararg
* android_api.nit: 1 / 23138 # From a multi-dimensional array
* android_api.nit: 21 / 23138 # From a vararg

All the unknown multi-dimensional array: (these could be added by hand to the library)
* int[][]
* java.lang.String[][]
* java.lang.annotation.Annotation[][]

Pull-Request: #1599
Reviewed-by: Jean Privat <jean@pryen.org>
Reviewed-by: Lucas Bajolet <r4pass@hotmail.com>

20 files changed:
contrib/jwrapper/Makefile
contrib/jwrapper/examples/android_api/.gitignore
contrib/jwrapper/examples/android_api/Makefile
contrib/jwrapper/examples/java_api/.gitignore [new file with mode: 0644]
contrib/jwrapper/examples/java_api/Makefile [new file with mode: 0644]
contrib/jwrapper/examples/java_api/api_user.nit [new file with mode: 0644]
contrib/jwrapper/examples/java_api/api_user.sav [new file with mode: 0644]
contrib/jwrapper/examples/queue/Makefile
contrib/jwrapper/grammar/javap.sablecc
contrib/jwrapper/src/code_generator.nit
contrib/jwrapper/src/javap_visitor.nit
contrib/jwrapper/src/jwrapper.nit
contrib/jwrapper/src/model.nit
contrib/jwrapper/tests/generics.javap [new file with mode: 0644]
contrib/jwrapper/tests/statics.javap [new file with mode: 0644]
lib/java/base.nit [new file with mode: 0644]
lib/java/collections.nit
lib/java/io.nit
lib/java/java.nit
lib/standard/collection/abstract_collection.nit

index 4a52abf..d8d05e0 100644 (file)
@@ -20,6 +20,10 @@ clean:
 
 check: bin/jwrapper tests/wildcards.javap
        mkdir -p tmp
+       bin/jwrapper -v -u stub -o tests/statics.nit tests/statics.javap
+       ../../bin/nitpick -q tests/statics.nit
+       bin/jwrapper -v -u comment -o tests/generics.nit tests/generics.javap
+       ../../bin/nitpick -q tests/generics.nit
        bin/jwrapper -v -u comment -o tests/long.nit tests/long.javap
        ../../bin/nitpick -q tests/long.nit
        bin/jwrapper -v -u comment -o tests/inits.nit tests/inits.javap
@@ -31,6 +35,7 @@ check: bin/jwrapper tests/wildcards.javap
        bin/jwrapper -v -u comment -o tests/wildcards.nit tests/wildcards.javap
        ../../bin/nitpick -q tests/wildcards.nit
        make -C examples/queue/ check
+       make -C examples/java_api/ check
 
 check-libs: bin/jwrapper
        # This config dependent rule must be tweaked according to each system
index e26ba9c..e01cb00 100644 (file)
@@ -4,11 +4,12 @@ all: android_api.nit
 
 java_api.nit:
        mkdir -p tmp
-       ../../bin/jwrapper -v -u comment -o java_api.nit -r "^java" $(ANDROID_JAR)
+       ../../bin/jwrapper -vv -u comment -o java_api.nit -r "^(java|javax|junit|org)" $(ANDROID_JAR) -i ../../../../lib/java/collections.nit
+       echo "+ Disabled functions: `grep '#\s*fun' $@ | wc -l` / `grep '^\s*fun' $@ | wc -l`"
 
 android_api.nit: java_api.nit
-       ../../bin/jwrapper -v -u comment -o android_api.nit -r "^android" -i java_api.nit $(ANDROID_JAR)
-       echo "+ Disabled functions: `grep '#    fun' $@ | wc -l` / `grep '^     fun' $@ | wc -l`"
+       ../../bin/jwrapper -vv -u comment -o android_api.nit -r "^(android|com.android)" -i java_api.nit $(ANDROID_JAR) -i ../../../../lib/java/collections.nit
+       echo "+ Disabled functions: `grep '#\s*fun' $@ | wc -l` / `grep '^\s*fun' $@ | wc -l`"
 
        # Insert an import between the 2 modules
        sed -i -e "s/import java/import java\nimport java_api/" android_api.nit
diff --git a/contrib/jwrapper/examples/java_api/.gitignore b/contrib/jwrapper/examples/java_api/.gitignore
new file mode 100644 (file)
index 0000000..f2f6834
--- /dev/null
@@ -0,0 +1,5 @@
+java_api.nit
+api_user
+api_user.res
+api_user.jar
+tmp/
diff --git a/contrib/jwrapper/examples/java_api/Makefile b/contrib/jwrapper/examples/java_api/Makefile
new file mode 100644 (file)
index 0000000..a98f013
--- /dev/null
@@ -0,0 +1,31 @@
+RT_JAR ?= /usr/lib/jvm/default-java/jre/lib/rt.jar
+
+all: api_user
+
+java_api.nit:
+       mkdir -p tmp
+       ../../bin/jwrapper -vv -u comment -o java_api.nit $(RT_JAR) \
+               -r "^java.(lang|util|io)" -i ../../../../lib/java/collections.nit
+       echo "+ Disabled functions: `grep '#\s*fun' $@ | wc -l` / `grep '^\s*fun' $@ | wc -l`"
+
+api_user: java_api.nit
+       # Using --semi-global makes it much faster
+       time -f "%E k:%S u:%U" ../../../../bin/nitc -v api_user.nit --semi-global
+
+check: api_user
+       ./api_user > api_user.res
+       diff api_user.sav api_user.res
+
+check-more-java:
+       mkdir -p tmp
+       ../../bin/jwrapper -vv -u comment -o java_api.nit $(RT_JAR) \
+               -r "^(java|org)" -i ../../../../lib/java/collections.nit
+       echo "+ Disabled functions: `grep '#\s*fun' $@ | wc -l` / `grep '^\s*fun' $@ | wc -l`"
+
+       # This may take a while...
+       time -f "%E k:%S u:%U" ../../../../bin/nitc -v api_user.nit --no-cc
+
+       # Don't compile the C only the Java
+       make -C nit_compile Nit_rt.class
+
+.PHONY: api_user java_api.nit
diff --git a/contrib/jwrapper/examples/java_api/api_user.nit b/contrib/jwrapper/examples/java_api/api_user.nit
new file mode 100644 (file)
index 0000000..b0f5a0c
--- /dev/null
@@ -0,0 +1,34 @@
+# This file is part of NIT ( http://www.nitlanguage.org ).
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import java_api
+
+# Get a Java string
+var str = java_lang_integer_to_string_int(5678)
+
+# Do some Java side printing
+var stdout = java_lang_system_out
+stdout.println_int 1234
+stdout.println_String str
+
+# Test a generic list
+var list = new Java_util_ArrayList
+
+print list.is_empty
+assert list.is_empty
+
+print list.size
+assert list.size == 0
+
+list.clear
diff --git a/contrib/jwrapper/examples/java_api/api_user.sav b/contrib/jwrapper/examples/java_api/api_user.sav
new file mode 100644 (file)
index 0000000..86d17d1
--- /dev/null
@@ -0,0 +1,4 @@
+1234
+5678
+true
+0
index a07438c..fc9a0ae 100644 (file)
@@ -1,5 +1,5 @@
 # Nit test program
-user_test: queue.nit $(shell ../../../../bin/nitls -M user_test.nit) ../../../../bin/nitc
+user_test: queue.nit $(shell ../../../../bin/nitls -M user_test.nit) ../../../../bin/nitc ../../bin/jwrapper
        CLASSPATH=`pwd` ../../../../bin/nitc user_test.nit
 
        # Manually add our class file to the Jar for easy access
index dc46557..718a446 100644 (file)
@@ -44,8 +44,8 @@ type_bound
        | {head:} full_class_name;
 
 generic_identifier
-       = full_class_name
-       | wildcard;
+       = {class:} full_class_name
+       | {wildcard:} wildcard;
 
 full_class_name
        = {tail:} full_class_name separator class_name
index 5e3d770..eed951a 100644 (file)
@@ -66,11 +66,16 @@ class CodeGenerator
                file_out.write "\n"
 
                for key, jclass in model.classes do
+                       # Skip anonymous classes
+                       if jclass.class_type.is_anonymous then continue
+
+                       # Skip classes with an invalid name at the Java language level
+                       if jclass.class_type.extern_equivalent.has("-") then continue
 
                        generate_class_header(jclass.class_type)
 
                        for id, signatures in jclass.methods do
-                               for signature in signatures do
+                               for signature in signatures do if not signature.is_static then
                                        generate_method(jclass, id, id, signature.return_type, signature.params)
                                        file_out.write "\n"
                                end
@@ -80,17 +85,41 @@ class CodeGenerator
                        for constructor in jclass.constructors do
                                var complex = jclass.constructors.length != 1 and constructor.params.not_empty
                                var base_name = if complex then "from" else ""
-                               var name = jclass.nit_name_for(base_name, constructor.params, complex)
+                               var name = jclass.nit_name_for(base_name, constructor.params, complex, false)
 
                                generate_constructor(jclass, constructor, name)
                        end
 
                        # Attributes
-                       for id, java_type in jclass.attributes do
-                               generate_getter_setter(jclass, id, java_type)
+                       for id, attribute in jclass.attributes do if not attribute.is_static then
+                               generate_getter_setter(jclass, id, attribute)
                        end
 
+                       # JNI services
+                       generate_jni_services jclass.class_type
+
+                       # Close the class
                        file_out.write "end\n\n"
+
+                       # Static functions as top-level methods
+                       var static_functions_prefix = jclass.class_type.extern_name.to_snake_case
+                       for id, signatures in jclass.methods do
+                               for signature in signatures do if signature.is_static then
+                                       var nit_id = static_functions_prefix + "_" + id
+                                       generate_method(jclass, id, nit_id, signature.return_type, signature.params, is_static=true)
+                                       file_out.write "\n"
+                               end
+                       end
+
+                       # Static attributes as top-level getters and setters
+                       for id, attribute in jclass.attributes do if attribute.is_static then
+                               generate_getter_setter(jclass, id, attribute)
+                       end
+
+                       # Primitive arrays
+                       for d in [1..opt_arrays.value] do
+                               generate_primitive_array(jclass, d)
+                       end
                end
 
                if stub_for_unknown_types then
@@ -123,8 +152,8 @@ class CodeGenerator
        private fun generate_class_header(jtype: JavaType)
        do
                var nit_type = model.java_to_nit_type(jtype)
-               file_out.write "# Java class: {jtype.to_package_name}\n"
-               file_out.write "extern class {nit_type} in \"Java\" `\{ {jtype.to_package_name} `\}\n"
+               file_out.write "# Java class: {jtype}\n"
+               file_out.write "extern class {nit_type} in \"Java\" `\{ {jtype.extern_equivalent} `\}\n"
                file_out.write "\tsuper JavaObject\n\n"
        end
 
@@ -132,105 +161,111 @@ class CodeGenerator
        do
                var nit_type = jtype.extern_name
 
-               file_out.write "extern class {nit_type} in \"Java\" `\{ {jtype.to_package_name} `\}\n"
+               file_out.write "extern class {nit_type} in \"Java\" `\{ {jtype.extern_equivalent} `\}\n"
                file_out.write "\tsuper JavaObject\n\nend\n"
        end
 
-       private fun generate_method(java_class: JavaClass, jmethod_id, method_id: String,
-               jreturn_type: JavaType, jparam_list: Array[JavaType])
+       private fun generate_method(java_class: JavaClass, java_method_id, method_id: String,
+               java_return_type: JavaType, java_params: Array[JavaType], is_static: nullable Bool)
        do
-               var java_params = ""
-               var nit_params  = ""
+               var java_args = new Array[String]
+               var nit_params = new Array[String]
                var nit_id = "arg"
                var nit_id_no = 0
-               var nit_types = new Array[NitType]
-               var comment = ""
+               var c = ""
 
                # Parameters
-               for i in [0..jparam_list.length[ do
-                       var jparam = jparam_list[i]
+               for jparam in java_params do
                        var nit_type = model.java_to_nit_type(jparam)
 
-                       if not nit_type.is_known and comment_unknown_types then comment = "#"
-                       if jparam.is_primitive_array then comment = "#"
+                       if not nit_type.is_known and comment_unknown_types then c = "#"
+                       if jparam.is_vararg then c = "#"
 
-                       var cast = jparam.param_cast
-
-                       nit_types.add(nit_type)
-
-                       if i == jparam_list.length - 1 then
-                               java_params += "{cast}{nit_id}{nit_id_no}"
-                               nit_params  += "{nit_id}{nit_id_no}: {nit_type}"
-                       else
-                               java_params += "{cast}{nit_id}{nit_id_no}" + ", "
-                               nit_params  += "{nit_id}{nit_id_no}: {nit_type}, "
-                       end
+                       java_args.add "{jparam.param_cast}{nit_id}{nit_id_no}"
+                       nit_params.add "{nit_id}{nit_id_no}: {nit_type}"
 
                        nit_id_no += 1
                end
 
-               # Method documentation
-               var doc = "\t# Java implementation: {java_class}.{jmethod_id}\n"
-
                # Method identifier
                method_id = method_id.to_nit_method_name
-               method_id = java_class.nit_name_for(method_id, jparam_list, java_class.methods[jmethod_id].length > 1)
+               method_id = java_class.nit_name_for(method_id, java_params, java_class.methods[java_method_id].length > 1, is_static == true)
+
+               # Build the signature
                var nit_signature = new Array[String]
+               nit_signature.add "fun {method_id}"
+               if not java_params.is_empty then nit_signature.add "({nit_params.join(", ")})"
+
+               # Return value
+               var return_type = null
+               if not java_return_type.is_void then
+                       return_type = model.java_to_nit_type(java_return_type)
 
-               nit_signature.add "\tfun {method_id}"
+                       if not return_type.is_known and comment_unknown_types then c = "#"
+                       if java_return_type.is_vararg then c = "#"
 
-               if not jparam_list.is_empty then
-                       nit_signature.add "({nit_params})"
+                       nit_signature.add ": " + return_type.to_s
                end
 
-               var return_type = null
-               if not jreturn_type.is_void then
-                       return_type = model.java_to_nit_type(jreturn_type)
+               # Build the call in Java
+               var java_call
+               if is_static == true then
+                       java_call = java_class.class_type.package_name
+               else java_call = "self"
+               java_call += ".{java_method_id}({java_args.join(", ")})"
 
-                       if not return_type.is_known and comment_unknown_types then comment = "#"
-                       if jreturn_type.is_primitive_array then comment = "#"
+               if return_type != null then java_call = "return {java_return_type.return_cast}" + java_call
 
-                       nit_signature.add ": {return_type} "
-               end
+               # Tabulation
+               var t = "\t"
+               if is_static == true then t = ""
+               var ct = c+t
 
-               file_out.write doc
-               file_out.write comment + nit_signature.join
-
-               if comment == "#" then
-                       file_out.write " in \"Java\" `\{\n{comment}\t\tself.{jmethod_id}({java_params});\n{comment}\t`\}\n"
-               # Methods with return type
-               else if return_type != null then
-                       file_out.write " in \"Java\" `\{\n{comment}\t\treturn {jreturn_type.return_cast}self.{jmethod_id}({java_params});\n{comment}\t`\}\n"
-               # Methods without return type
-               else if jreturn_type.is_void then
-                       file_out.write " in \"Java\" `\{\n{comment}\t\tself.{jmethod_id}({java_params});\n{comment}\t`\}\n"
-               # No copy
-               else
-                       file_out.write " in \"Java\" `\{\n{comment}\t\tself.{jmethod_id}({java_params});\n{comment}\t`\}\n"
-               end
+               # Write
+               file_out.write """
+{{{t}}}# Java implementation: {{{java_return_type}}} {{{java_class}}}.{{{java_method_id}}}({{{java_params.join(", ")}}})
+{{{ct}}}{{{nit_signature.join}}} in "Java" `{
+{{{ct}}}       {{{java_call}}};
+{{{ct}}}`}
+"""
        end
 
        # Generate getter and setter to access an attribute, of field
-       private fun generate_getter_setter(java_class: JavaClass, java_id: String, java_type: JavaType)
+       private fun generate_getter_setter(java_class: JavaClass, java_id: String,
+               attribute: JavaAttribute)
        do
+               var java_type = attribute.java_type
                var nit_type = model.java_to_nit_type(java_type)
-               var nit_id = java_id.to_nit_method_name
-               nit_id = java_class.nit_name_for(nit_id, [java_type], false)
+
+               var nit_id = java_id
+               if attribute.is_static then nit_id = java_class.class_type.extern_name.to_snake_case + "_" + nit_id
+               nit_id = nit_id.to_nit_method_name
+               nit_id = java_class.nit_name_for(nit_id, [java_type], false, attribute.is_static)
 
                var c = ""
                if not nit_type.is_known and comment_unknown_types then c = "#"
-               if java_type.is_primitive_array then c = "#"
+               if java_type.is_vararg then c = "#"
+
+               var recv
+               if attribute.is_static then
+                       recv = java_class.class_type.package_name
+               else recv = "self"
+
+               # Tabulation
+               var t = "\t"
+               if attribute.is_static then t = ""
+               var ct = c+t
 
                file_out.write """
-       # Java getter: {{{java_class}}}.{{{java_id}}}
-{{{c}}}        fun {{{nit_id}}}: {{{nit_type}}} in "Java" `{
-{{{c}}}                return self.{{{java_id}}};
-{{{c}}}        `}
+{{{t}}}# Java getter: {{{java_class}}}.{{{java_id}}}
+{{{ct}}}fun {{{nit_id}}}: {{{nit_type}}} in "Java" `{
+{{{ct}}}       return {{{recv}}}.{{{java_id}}};
+{{{ct}}}`}
 
-       # Java setter: {{{java_class}}}.{{{java_id}}}
-{{{c}}}        fun {{{nit_id}}}=(value: {{{nit_type}}}) in "Java" `{
-{{{c}}}                self.{{{java_id}}} = value;
-{{{c}}}        `}
+{{{t}}}# Java setter: {{{java_class}}}.{{{java_id}}}
+{{{ct}}}fun {{{nit_id}}}=(value: {{{nit_type}}}) in "Java" `{
+{{{ct}}}       {{{recv}}}.{{{java_id}}} = value;
+{{{ct}}}`}
 
 """
        end
@@ -255,7 +290,7 @@ class CodeGenerator
                                param_id = param_id.successor(1)
 
                                if not nit_type.is_known and comment_unknown_types then c = "#"
-                               if java_type.is_primitive_array then c = "#"
+                               if java_type.is_vararg then c = "#"
                        end
 
                        nit_params_s = "(" + nit_params.join(", ") + ")"
@@ -265,11 +300,62 @@ class CodeGenerator
                file_out.write """
        # Java constructor: {{{java_class}}}
 {{{c}}}        new {{{name}}}{{{nit_params_s}}} in "Java" `{
-{{{c}}}                return new {{{java_class}}}({{{java_params_s}}});
+{{{c}}}                return new {{{java_class.class_type.package_name}}}({{{java_params_s}}});
 {{{c}}}        `}
 
 """
        end
+
+       private fun generate_primitive_array(java_class: JavaClass, dimensions: Int)
+       do
+               var base_java_type = java_class.class_type
+               var java_type = base_java_type.clone
+               java_type.array_dimension = dimensions
+
+               var base_nit_type = model.java_to_nit_type(base_java_type)
+               var nit_type = model.java_to_nit_type(java_type)
+
+               file_out.write """
+# Java primitive array: {{{java_type}}}
+extern class {{{nit_type}}} in "Java" `{ {{{java_type.extern_equivalent}}} `}
+       super AbstractJavaArray[{{{base_nit_type}}}]
+
+       # Get a new array of the given `size`
+       new(size: Int) in "Java" `{ return new {{{base_java_type}}}[(int)size]; `}
+
+       redef fun [](i) in "Java" `{ return self[(int)i]; `}
+
+       redef fun []=(i, e) in "Java" `{ self[(int)i] = e; `}
+
+       redef fun length in "Java" `{ return self.length; `}
+
+"""
+               generate_jni_services(java_type)
+               file_out.write """
+end
+
+"""
+       end
+
+       # Generate JNI related services
+       #
+       # For now, mostly avoid issue #845, but more services could be generated as needed.
+       private fun generate_jni_services(java_type: JavaType)
+       do
+               var nit_type = model.java_to_nit_type(java_type)
+
+               file_out.write """
+       redef fun new_global_ref import sys, Sys.jni_env `{
+               Sys sys = {{{nit_type}}}_sys(self);
+               JNIEnv *env = Sys_jni_env(sys);
+               return (*env)->NewGlobalRef(env, self);
+       `}
+
+       redef fun pop_from_local_frame_with_env(jni_env) `{
+               return (*jni_env)->PopLocalFrame(jni_env, self);
+       `}
+"""
+       end
 end
 
 redef class Sys
@@ -283,10 +369,16 @@ redef class Sys
                "protected", "public", "return", "self", "super", "then", "true", "type", "var", "while",
 
        # Top-level methods
-               "class_name", "get_time", "hash", "is_same_type", "is_same_instance", "output",
+               "class_name", "get_time", "hash", "inspect", "inspect_head", "is_same_type",
+               "is_same_instance", "object_id", "output", "output_class_name", "sys", "to_s",
 
        # Pointer or JavaObject methods
                "free"])
+
+       # Name of methods used at the top-level
+       #
+       # Used by `JavaClass::nit_name_for` with static properties.
+       private var top_level_used_names = new HashSet[String]
 end
 
 redef class String
@@ -317,13 +409,13 @@ end
 
 redef class JavaClass
        # Property names used in this class
-       private var used_name = new HashSet[String]
+       private var used_names = new HashSet[String]
 
        # Get an available property name for the Java property with `name` and parameters
        #
        # If `use_parameters_name` then expect that there will be conflicts,
        # so use the types of `parameters` to build the name.
-       private fun nit_name_for(name: String, parameters: Array[JavaType], use_parameters_name: Bool): String
+       private fun nit_name_for(name: String, parameters: Array[JavaType], use_parameters_name: Bool, is_static: Bool): String
        do
                # Append the name of each parameter
                if use_parameters_name then
@@ -332,15 +424,21 @@ redef class JavaClass
                        end
                end
 
+               # Set of property names, local or top-level
+               var used_names
+               if is_static then
+                       used_names = sys.top_level_used_names
+               else used_names = self.used_names
+
                # As a last resort, append numbers to the name
                var base_name = name
                var count = 1
-               while used_name.has(name) do
+               while used_names.has(name) do
                        name = base_name + count.to_s
                        count += 1
                end
 
-               used_name.add name
+               used_names.add name
                return name
        end
 end
index c3e5f98..fe81c88 100644 (file)
@@ -80,9 +80,20 @@ end
 redef class Nproperty_declaration_method
        redef fun accept_visitor(v)
        do
+               var is_static = false
+               var modifiers = n_modifier
+               if modifiers != null then is_static = modifiers.has_static
+
                var id = n_identifier.text
                var return_jtype = n_type.to_java_type
 
+               # Generic parameters
+               var n_params = n_generic_parameters
+               var generic_params
+               if n_params != null then
+                       generic_params = n_params.n_parameters.to_a
+               else generic_params = new Array[JavaType]
+
                # Collect parameters
                var n_parameters = n_parameters
                var params
@@ -90,7 +101,7 @@ redef class Nproperty_declaration_method
                        params = n_parameters.to_a
                else params = new Array[JavaType]
 
-               var method = new JavaMethod(return_jtype, params)
+               var method = new JavaMethod(is_static, return_jtype, params, generic_params)
                v.java_class.methods[id].add method
        end
 end
@@ -106,7 +117,14 @@ redef class Nproperty_declaration_constructor
                        params = n_parameters.to_a
                else params = new Array[JavaType]
 
-               var method = new JavaConstructor(params)
+               # Generic parameters
+               var n_params = n_generic_parameters
+               var generic_params
+               if n_params != null then
+                       generic_params = n_params.n_parameters.to_a
+               else generic_params = new Array[JavaType]
+
+               var method = new JavaConstructor(params, generic_params)
                v.java_class.constructors.add method
        end
 end
@@ -122,7 +140,11 @@ redef class Nproperty_declaration_attribute
                var brackets = n_brackets
                if brackets != null then jtype.array_dimension += brackets.children.length
 
-               v.java_class.attributes[id] = jtype
+               var is_static = false
+               var modifiers = n_modifier
+               if modifiers != null then is_static = modifiers.has_static
+
+               v.java_class.attributes[id] = new JavaAttribute(is_static, jtype)
        end
 end
 
@@ -153,7 +175,6 @@ redef class Nbase_type
        private fun to_java_type: JavaType
        do
                # By default, everything is bound by object
-               # TODO use a more precise bound
                var jtype = new JavaType
                jtype.identifier.add_all(["java", "lang", "object"])
                return jtype
@@ -189,6 +210,41 @@ redef class Nbase_type_void
        end
 end
 
+redef class Nbase_type_extends
+       redef fun to_java_type do return n_generic_identifier.to_java_type
+end
+
+redef class Nbase_type_super
+       redef fun to_java_type
+       do
+               var bounds = n_type_bound.to_a
+
+               # Java use more than one lower bound,
+               # it can't be translated statically to Nit,
+               # so we use the only the first one.
+               # This may cause problems on complex generic types,
+               # but these cases can be handled manually.
+               return bounds.first
+       end
+end
+
+redef class Ngeneric_identifier
+       private fun to_java_type: JavaType is abstract
+end
+
+redef class Ngeneric_identifier_class
+       redef fun to_java_type do return n_full_class_name.to_java_type
+end
+
+redef class Ngeneric_identifier_wildcard
+       redef fun to_java_type
+       do
+               var jtype = new JavaType
+               jtype.identifier.add_all(["java", "lang", "Object"])
+               return jtype
+       end
+end
+
 redef class Nfull_class_name
        # All the identifiers composing this class name
        private fun to_a: Array[String] is abstract
@@ -257,3 +313,32 @@ redef class Nparameter
                return jtype
        end
 end
+
+redef class Nodes
+       private fun has_static: Bool
+       do
+               for modifier in depth do
+                       if modifier isa NToken and modifier.text == "static" then return true
+               end
+
+               return false
+       end
+end
+
+redef class Ntype_bound
+       # Get the types composing this bound
+       private fun to_a: Array[JavaType] is abstract
+end
+
+redef class Ntype_bound_head
+       redef fun to_a do return [n_full_class_name.to_java_type]
+end
+
+redef class Ntype_bound_tail
+       redef fun to_a
+       do
+               var a = n_type_bound.to_a
+               a.add n_full_class_name.to_java_type
+               return a
+       end
+end
index 04878da..0a84fa9 100644 (file)
@@ -44,7 +44,7 @@ var opt_output = new OptionString("Output file", "-o")
 var opt_regex = new OptionString("Regex pattern to filter classes in Jar archives", "-r")
 var opt_help = new OptionBool("Show this help message", "-h", "--help")
 
-opts.add_option(opt_output, opt_unknown, opt_extern_class_prefix, opt_libs, opt_regex, opt_cast_objects, opt_verbose, opt_help)
+opts.add_option(opt_output, opt_unknown, opt_extern_class_prefix, opt_libs, opt_regex, opt_cast_objects, opt_arrays, opt_verbose, opt_help)
 opts.parse args
 
 if opts.errors.not_empty or opts.rest.is_empty or opt_help.value then
@@ -192,6 +192,9 @@ var visitor = new JavaVisitor(model)
 visitor.enter_visit root_node
 sys.perfs["core model"].add clock.lapse
 
+model.resolve_types
+sys.perfs["core resolve"].add clock.lapse
+
 if opt_verbose.value > 0 then print "# Generating Nit code"
 
 var use_comment = opt_unknown.value == 0
@@ -203,4 +206,15 @@ sys.perfs["code generator"].add clock.lapse
 if opt_verbose.value > 1 then
        print "# Performance Analysis:"
        print sys.perfs
+
+       print "# {model.unknown_types.length} unknown types:"
+       var c = 0
+       for id, ntype in model.unknown_types do
+               print "* {id}"
+               c += 1
+               if c > 100 then
+                       print "* ..."
+                       break
+               end
+       end
 end
index 8d6da40..50d54f1 100644 (file)
@@ -24,7 +24,14 @@ import opts
 import jtype_converter
 
 class JavaType
+       super Cloneable
+
+       # Identifiers composing the namespace and class name
+       #
+       # An array of all the names that would be separated by `.`.
+       # Each name may contain `$`.
        var identifier = new Array[String]
+
        var generic_params: nullable Array[JavaType] = null
 
        # Is this a void return type?
@@ -33,6 +40,16 @@ class JavaType
        # Is this type a vararg?
        var is_vararg = false is writable
 
+       # Is this type based on an anonymous class?
+       var is_anonymous: Bool is lazy do
+               for id in identifier do
+                       for part in id.split("$") do
+                               if part.chars.first.is_digit then return true
+                       end
+               end
+               return false
+       end
+
        # Has some generic type to be resolved (T extends foo => T is resolved to foo)
        var has_unresolved_types = false
 
@@ -42,8 +59,6 @@ class JavaType
        fun is_primitive_array: Bool do return array_dimension > 0
 
        fun has_generic_params: Bool do return not generic_params == null
-       fun full_id: String do return identifier.join(".")
-       fun id: String do return identifier.last.replace("$", "")
 
        fun return_cast: String do return converter.cast_as_return(self.id)
 
@@ -80,14 +95,27 @@ class JavaType
                        name = prefix + id
                end
 
+               if is_primitive_array then
+                       name += "_" + "Array" * array_dimension
+               end
+
                name = name.replace("-", "_")
                name = name.replace("$", "_")
                return name
        end
 
-       redef fun to_s
-       do
-               var id = self.full_id
+       # Short name of the class, mangled to remove `$` (e.g. `Set`)
+       fun id: String do return identifier.last.replace("$", "")
+
+       # Full name of this class as used in an importation (e.g. `java.lang.Set`)
+       fun package_name: String do return identifier.join(".")
+
+       # Name of this class for the extern declaration in Nit (e.g. `java.lang.Set[]`)
+       fun extern_equivalent: String do return package_name + "[]" * array_dimension
+
+       # Full name of this class with arrays and generic values (e.g. `java.lang.Set<E>[]`)
+       redef fun to_s do
+               var id = self.package_name
 
                if self.is_primitive_array then
                        id += "[]" * array_dimension
@@ -99,34 +127,24 @@ class JavaType
                return id
        end
 
-       # To fully qualified package name
-       # Cuts the primitive array `[]`
-       fun to_package_name: String
+       # Get a copy of `self`
+       redef fun clone
        do
-               var str = self.to_s
-               var len = str.length
-
-               return str.substring(0, len - (2*array_dimension))
-       end
-
-       fun resolve_types(conversion_map: HashMap[String, Array[String]])
-       do
-               if identifier.length == 1 then
-                       var resolved_id = conversion_map.get_or_null(self.id)
-                       if resolved_id != null then self.identifier = new Array[String].from(resolved_id)
-               end
-
-               if self.has_generic_params then
-                       for params in generic_params do params.resolve_types(conversion_map)
-               end
+               var jtype = new JavaType
+               jtype.identifier = identifier
+               jtype.generic_params = generic_params
+               jtype.is_void = is_void
+               jtype.is_vararg = is_vararg
+               jtype.array_dimension = array_dimension
+               return jtype
        end
 
        # Comparison based on fully qualified named
        redef fun ==(other) do return other isa JavaType and
-               self.full_id == other.full_id and
-               self.is_primitive_array == other.is_primitive_array
+               self.package_name == other.package_name and
+               self.array_dimension == other.array_dimension
 
-       redef fun hash do return self.full_id.hash
+       redef fun hash do return self.package_name.hash
 end
 
 class NitType
@@ -148,7 +166,7 @@ class JavaClass
        var class_type: JavaType
 
        # Attributes of this class
-       var attributes = new HashMap[String, JavaType]
+       var attributes = new HashMap[String, JavaAttribute]
 
        # Methods of this class organized by their name
        var methods = new MultiHashMap[String, JavaMethod]
@@ -160,6 +178,58 @@ class JavaClass
        var imports = new HashSet[NitModule]
 
        redef fun to_s do return class_type.to_s
+
+       # Resolve the types in `other` in the context of this class
+       private fun resolve_types_of(other: JavaClass)
+       do
+               # Methods
+               for mid, method in other.methods do
+                       for signature in method do
+                               self.resolve(signature.return_type, signature.generic_params)
+                               for param in signature.params do self.resolve(param, signature.generic_params)
+                       end
+               end
+
+               # Constructors
+               for signature in other.constructors do
+                       for param in signature.params do self.resolve(param, signature.generic_params)
+               end
+
+               # Attributes
+               for aid, attribute in other.attributes do
+                       self.resolve attribute.java_type
+               end
+       end
+
+       # Resolve `java_type` in the context of this class
+       #
+       # Replace, in place, parameter types by their bound.
+       private fun resolve(java_type: JavaType, property_generic_params: nullable Array[JavaType])
+       do
+               # Skip types with a full package name
+               if java_type.identifier.length != 1 then return
+
+               # Skip primitive types
+               if converter.type_map.keys.has(java_type.id) then return
+
+               # Gather the generic parameters of the method, then the class
+               var params = new Array[JavaType]
+               if property_generic_params != null then params.add_all property_generic_params
+               var class_generic_params = class_type.generic_params
+               if class_generic_params != null then params.add_all class_generic_params
+
+               # Skip if there is not parameters usable to resolve
+               if params.is_empty then return
+
+               for param in params do
+                       if param.identifier == java_type.identifier then
+                               # Found a marching parameter type
+                               # TODO use a more precise bound
+                               java_type.identifier = ["java", "lang", "Object"]
+                               return
+                       end
+               end
+       end
 end
 
 # Model of all the Java class analyzed in one run
@@ -171,15 +241,15 @@ class JavaModel
        # Add a class in `classes`
        fun add_class(jclass: JavaClass)
        do
-               var key = jclass.class_type.full_id
+               var key = jclass.class_type.package_name
                classes[key] = jclass
        end
 
        # Unknown types, not already wrapped and not in this pass
-       private var unknown_types = new HashMap[JavaType, NitType]
+       var unknown_types = new HashMap[JavaType, NitType]
 
        # Wrapped types, or classes analyzed in this pass
-       private var known_types = new HashMap[JavaType, NitType]
+       var known_types = new HashMap[JavaType, NitType]
 
        # Get the `NitType` corresponding to the `JavaType`
        #
@@ -203,16 +273,17 @@ class JavaModel
                end
 
                # Is being wrapped in this pass?
-               var key = jtype.full_id
+               var key = jtype.package_name
                if classes.keys.has(key) then
-                       var nit_type = new NitType(jtype.extern_name)
-                       known_types[jtype] = nit_type
-
-                       return nit_type
+                       if jtype.array_dimension <= opt_arrays.value then
+                               var nit_type = new NitType(jtype.extern_name)
+                               known_types[jtype] = nit_type
+                               return nit_type
+                       end
                end
 
                # Search in lib
-               var nit_type = find_extern_class[jtype.full_id]
+               var nit_type = find_extern_class[jtype.extern_equivalent]
                if nit_type != null then
                        known_types[jtype] = nit_type
                        return nit_type
@@ -224,21 +295,62 @@ class JavaModel
                unknown_types[jtype] = nit_type
                return nit_type
        end
+
+       # Resolve the types in methods and attributes of this class
+       fun resolve_types
+       do
+               for id, java_class in classes do
+                       java_class.resolve_types_of java_class
+
+                       # Ask nester classes for resolve too
+                       var matches = id.search_all("$")
+                       for match in matches do
+                               var nester_name = id.substring(0, match.from)
+                               if classes.keys.has(nester_name) then
+                                       var nester = classes[nester_name]
+                                       nester.resolve_types_of java_class
+                               end
+                       end
+               end
+       end
+end
+
+# A property to a Java class
+abstract class JavaProperty
+
+       # Is this property marked static?
+       var is_static: Bool
 end
 
 # A Java method, with its signature
 class JavaMethod
+       super JavaProperty
+
        # Type returned by the method
        var return_type: JavaType
 
        # Type of the arguments of the method
        var params: Array[JavaType]
+
+       # Generic parameters of this method
+       var generic_params: Array[JavaType]
+end
+
+# An attribute in a Java class
+class JavaAttribute
+       super JavaProperty
+
+       # Type of the attribute
+       var java_type: JavaType
 end
 
 # A Java method, with its signature
 class JavaConstructor
        # Type of the parameters of this constructor
        var params: Array[JavaType]
+
+       # Generic parameters of this constructor
+       var generic_params: Array[JavaType]
 end
 
 # A Nit module, use to import the referenced extern classes
@@ -257,7 +369,7 @@ end
 redef class Sys
        # Collection of Java classes already wrapped in the library
        #
-       # * The key is from `JavaType.full_id`.
+       # * The key uses `JavaType.to_s`.
        # * The value is the corresponding `NitType`.
        var find_extern_class: DefaultMap[String, nullable NitType] is lazy do
                var map = new DefaultMap[String, nullable NitType](null)
@@ -290,16 +402,16 @@ redef class Sys
                grep.wait
 
                # Sort out the modules, Nit class names and Java types
-               var regex = """(.+):\\s*extern +class +([a-zA-Z0-9_]+) *in *"Java" *`\\{ *([a-zA-Z0-9.$/]+) *`\\}""".to_re
+               var regex = """(.+):\\s*extern +class +([a-zA-Z0-9_]+) *in *"Java" *`\\{(.+)`\\}""".to_re
                for line in lines do
                        var matches = line.search_all(regex)
                        for match in matches do
                                var path = match[1].to_s
                                var nit_name = match[2].to_s
-                               var java_name = match[3].to_s
+                               var java_name = match[3].to_s.trim
 
                                # Debug code
-                               # print "+ Found {nit_name}:{java_name} at {path}"
+                               # print "+ Found {nit_name}: {java_name} at {path}"
 
                                var mod = modules.get_or_null(path)
                                if mod == null then
@@ -322,6 +434,9 @@ redef class Sys
 
        # Libraries to search for existing wrappers
        var opt_libs = new OptionArray("Paths to libraries with wrappers of Java classes ('auto' to use the full Nit lib)", "-i")
+
+       # Generate the primitive array version of each class up to the given depth
+       var opt_arrays = new OptionInt("Depth of the primitive array for each wrapped class (default: 1)", 1, "-a")
 end
 
 redef class Text
diff --git a/contrib/jwrapper/tests/generics.javap b/contrib/jwrapper/tests/generics.javap
new file mode 100644 (file)
index 0000000..0753c90
--- /dev/null
@@ -0,0 +1,6 @@
+public abstract class android.os.asynctask<params, progress, result> {
+  public android.os.asynctask();
+  public final android.os.asynctask$status getstatus();
+  public final result get(long, java.util.concurrent.timeunit) throws java.lang.interruptedexception, java.util.concurrent.executionexception, java.util.concurrent.timeoutexception;
+  public final android.os.asynctask<params, progress, result> execute(params...);
+}
diff --git a/contrib/jwrapper/tests/statics.javap b/contrib/jwrapper/tests/statics.javap
new file mode 100644 (file)
index 0000000..a10cc75
--- /dev/null
@@ -0,0 +1,18 @@
+public final class com.oracle.net.Sdp {
+  public static java.net.Socket openSocket() throws java.io.IOException;
+  public static java.net.ServerSocket openServerSocket() throws java.io.IOException;
+  public static java.nio.channels.SocketChannel openSocketChannel() throws java.io.IOException;
+  public static java.nio.channels.ServerSocketChannel openServerSocketChannel() throws java.io.IOException;
+}
+public class com.sun.activation.registries.LogSupport {
+  public static void log(java.lang.String);
+  public static void log(java.lang.String, java.lang.Throwable);
+  public static boolean isLoggable();
+}
+public class com.sun.activation.registries.MailcapTokenizer {
+  public static final int UNKNOWN_TOKEN;
+}
+public final class com.sun.beans.TypeResolver {
+  public com.sun.beans.TypeResolver();
+  public static java.lang.reflect.Type resolveInClass(java.lang.Class<?>, java.lang.reflect.Type);
+}
diff --git a/lib/java/base.nit b/lib/java/base.nit
new file mode 100644 (file)
index 0000000..0db04a4
--- /dev/null
@@ -0,0 +1,195 @@
+# This file is part of NIT ( http://www.nitlanguage.org ).
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# Supporting services for the FFI with Java
+#
+# This modules relies on `Sys::jvm`, `Sys::jni_env` and
+# `Sys::create_default_jvm` to get a handle on a JVM. You can adapt the
+# behavior of the FFI and services in this module by redefing
+# `Sys::create_default_jvm` and supply your own JVM object. You can manage
+# multiple java thread by switching the current environment in a redef
+# of `Sys::jni_env`, and multiple JVM using `Sys::jvm`.
+module base is
+       cflags "-I $(JAVA_HOME)/include/ -I $(JAVA_HOME)/include/linux/"
+       ldflags "-L $(JNI_LIB_PATH) -ljvm"
+       new_annotation extra_java_files
+end
+
+import jvm
+
+redef class Sys
+       private var jvm_cache: nullable JavaVM = null
+       private var jni_env_cache: nullable JniEnv = null
+
+       # Default Java Virtual Machine to use (will be instantiated using
+       # `create_default_jvm` if not already set)
+       fun jvm: JavaVM
+       do
+               if jvm_cache == null then create_default_jvm
+               return jvm_cache.as(not null)
+       end
+
+       # Sets the current default Java Virtual Machine (use with `jni_env=`)
+       fun jvm=(jvm: JavaVM) do jvm_cache = jvm
+
+       # Current main `JniEnv`
+       fun jni_env: JniEnv
+       do
+               if jni_env_cache == null then create_default_jvm
+               return jni_env_cache.as(not null)
+       end
+
+       # Sets the current default JNI env (use with `jvm=`)
+       fun jni_env=(jni_env: JniEnv) do jni_env_cache = jni_env
+
+       # Called by `jvm` and `jni_env` to instantiate a Java Virtual Machine.
+       # Used mostly for the FFI with Java.
+       protected fun create_default_jvm
+       do
+               var builder = new JavaVMBuilder
+
+               # By default, look for Java classes in a jar file the same directory as the executable
+               builder.options.add "-Djava.class.path={sys.program_name}.jar"
+
+               var jvm = builder.create_jvm
+               assert jvm != null else print "JVM creation failed"
+
+               self.jvm = jvm
+               self.jni_env = builder.jni_env.as(not null)
+       end
+
+       # Get a Java class by its name from the current `jni_env`
+       fun load_jclass(name: NativeString): JClass import jni_env `{
+               JNIEnv *nit_ffi_jni_env = Sys_jni_env(self);
+
+               // retrieve the implementation Java class
+               jclass java_class = (*nit_ffi_jni_env)->FindClass(nit_ffi_jni_env, name);
+               if (java_class == NULL) {
+                       fprintf(stderr, "Nit FFI with Java error: failed to load class.\\n");
+                       (*nit_ffi_jni_env)->ExceptionDescribe(nit_ffi_jni_env);
+                       exit(1);
+               }
+
+               return java_class;
+       `}
+end
+
+# A standard Java string `java.lang.String`
+#
+# Converted to a Nit string using `to_s`, or to a C string with `to_cstring`.
+# Created using `String::to_java_string` or `NativeString::to_java_string`.
+extern class JavaString in "Java" `{ java.lang.String `}
+       super JavaObject
+
+       # Get the string from Java and copy it to Nit memory
+       fun to_cstring: NativeString import sys, Sys.jni_env `{
+               Sys sys = JavaString_sys(self);
+               JNIEnv *env = Sys_jni_env(sys);
+
+               // Get the data from Java
+               const char *java_cstr = (*env)->GetStringUTFChars(env, self, NULL);
+               jsize len = (*env)->GetStringUTFLength(env, self);
+
+               // Copy it in control of Nit
+               char *nit_cstr = (char*)malloc(len+1);
+               memcpy(nit_cstr, java_cstr, len);
+               nit_cstr[len] = '\0';
+
+               // Free JNI ref and return
+               (*env)->ReleaseStringUTFChars(env, self, java_cstr);
+               return nit_cstr;
+       `}
+
+       redef fun to_s do return to_cstring.to_s
+end
+
+redef class NativeString
+       # Get a Java string from this C string
+       #
+       # This instance is only valid until the next execution of Java code.
+       # You can use `new_local_ref` to keep it longer.
+       fun to_java_string: JavaString import sys, Sys.jni_env `{
+               Sys sys = JavaString_sys(self);
+               JNIEnv *env = Sys_jni_env(sys);
+               return (*env)->NewStringUTF(env, self);
+       `}
+end
+
+redef class Text
+       # Get `self` as a `JavaString`
+       fun to_java_string: JavaString do return to_cstring.to_java_string
+end
+
+redef extern class JavaObject
+
+       # Returns a global reference to the Java object behind this reference
+       #
+       # You must use a global reference when keeping a Java object
+       # across execution of Java code, per JNI specification.
+       fun new_global_ref: SELF import sys, Sys.jni_env `{
+               Sys sys = JavaObject_sys(self);
+               JNIEnv *env = Sys_jni_env(sys);
+               return (*env)->NewGlobalRef(env, self);
+       `}
+
+       # Delete this global reference
+       fun delete_global_ref import sys, Sys.jni_env `{
+               Sys sys = JavaObject_sys(self);
+               JNIEnv *env = Sys_jni_env(sys);
+               (*env)->DeleteGlobalRef(env, self);
+       `}
+
+       # Delete this local reference
+       fun delete_local_ref import sys, Sys.jni_env `{
+               Sys sys = JavaObject_sys(self);
+               JNIEnv *env = Sys_jni_env(sys);
+               (*env)->DeleteLocalRef(env, self);
+       `}
+
+       # Pops the current local reference frame and return a valid reference to self
+       #
+       # Similar to `JavaVM::pop_local_frame` but returns a value.
+       fun pop_from_local_frame: SELF
+       do
+               var jni_env = sys.jni_env
+               return pop_from_local_frame_with_env(jni_env)
+       end
+
+       # Java implementation of `pop_from_local_frame`
+       protected fun pop_from_local_frame_with_env(jni_env: JniEnv): SELF `{
+               return (*jni_env)->PopLocalFrame(jni_env, self);
+       `}
+
+       # Is `self` null in Java?
+       #
+       # Since Java type system doesn't have the same `nullable` concept as Nit's,
+       # the two systems are not directly compatible. Any Nit instances of
+       # `JavaObject` may hold a Java null.
+       #
+       # To benefit from the safer type system of Nit, it is recommended to check
+       # the return of all extern methods implemented in Java to ensure the value
+       # is not a Java null. In case it is, you should replace it by a normal Nit
+       # `null`.
+       fun is_java_null: Bool in "Java" `{ return self == null; `}
+
+       # `JavaString` representation of `self` using Java's `toString`
+       fun to_java_string: JavaString in "Java" `{ return self.toString(); `}
+
+       # Use Java's `toString` for any `JavaObject`
+       redef fun to_s
+       do
+               if is_java_null then return super
+               return to_java_string.to_s
+       end
+end
index 0c24718..097158f 100644 (file)
@@ -28,7 +28,7 @@
 # ~~~
 module collections
 
-import java
+import base
 
 # Java primitive array
 #
index 6731c85..40d3f02 100644 (file)
@@ -19,7 +19,7 @@
 # This module is used by `android::assets_and_resources` and `android::audio`.
 module io
 
-import java
+import base
 
 in "Java" `{
        import java.io.File;
index 04e8743..116f40d 100644 (file)
@@ -1,7 +1,5 @@
 # This file is part of NIT ( http://www.nitlanguage.org ).
 #
-# Copyright 2014 Alexis Laferrière <alexis.laf@xymus.net>
-#
 # Licensed under the Apache License, Version 2.0 (the "License");
 # you may not use this file except in compliance with the License.
 # You may obtain a copy of the License at
 # The module `jvm` gives more control over the JVM instances and wraps
 # most of JNI functions. You can use it to further customize the behavior
 # of your code.
-module java is
-       cflags "-I $(JAVA_HOME)/include/ -I $(JAVA_HOME)/include/linux/"
-       ldflags "-L $(JNI_LIB_PATH) -ljvm"
-       new_annotation extra_java_files
-end
-
-import jvm
-
-redef class Sys
-       private var jvm_cache: nullable JavaVM = null
-       private var jni_env_cache: nullable JniEnv = null
-
-       # Default Java Virtual Machine to use (will be instantiated using
-       # `create_default_jvm` if not already set)
-       fun jvm: JavaVM
-       do
-               if jvm_cache == null then create_default_jvm
-               return jvm_cache.as(not null)
-       end
-
-       # Sets the current default Java Virtual Machine (use with `jni_env=`)
-       fun jvm=(jvm: JavaVM) do jvm_cache = jvm
-
-       # Current main `JniEnv`
-       fun jni_env: JniEnv
-       do
-               if jni_env_cache == null then create_default_jvm
-               return jni_env_cache.as(not null)
-       end
-
-       # Sets the current default JNI env (use with `jvm=`)
-       fun jni_env=(jni_env: JniEnv) do jni_env_cache = jni_env
-
-       # Called by `jvm` and `jni_env` to instantiate a Java Virtual Machine.
-       # Used mostly for the FFI with Java.
-       protected fun create_default_jvm
-       do
-               var builder = new JavaVMBuilder
-
-               # By default, look for Java classes in a jar file the same directory as the executable
-               builder.options.add "-Djava.class.path={sys.program_name}.jar"
-
-               var jvm = builder.create_jvm
-               assert jvm != null else print "JVM creation failed"
-
-               self.jvm = jvm
-               self.jni_env = builder.jni_env.as(not null)
-       end
-
-       # Get a Java class by its name from the current `jni_env`
-       fun load_jclass(name: NativeString): JClass import jni_env `{
-               JNIEnv *nit_ffi_jni_env = Sys_jni_env(self);
-
-               // retrieve the implementation Java class
-               jclass java_class = (*nit_ffi_jni_env)->FindClass(nit_ffi_jni_env, name);
-               if (java_class == NULL) {
-                       fprintf(stderr, "Nit FFI with Java error: failed to load class.\\n");
-                       (*nit_ffi_jni_env)->ExceptionDescribe(nit_ffi_jni_env);
-                       exit(1);
-               }
-
-               return java_class;
-       `}
-end
-
-# A standard Java string `java.lang.String`
-#
-# Converted to a Nit string using `to_s`, or to a C string with `to_cstring`.
-# Created using `String::to_java_string` or `NativeString::to_java_string`.
-extern class JavaString in "Java" `{ java.lang.String `}
-       super JavaObject
-
-       # Get the string from Java and copy it to Nit memory
-       fun to_cstring: NativeString import sys, Sys.jni_env `{
-               Sys sys = JavaString_sys(self);
-               JNIEnv *env = Sys_jni_env(sys);
-
-               // Get the data from Java
-               const char *java_cstr = (*env)->GetStringUTFChars(env, self, NULL);
-               jsize len = (*env)->GetStringUTFLength(env, self);
-
-               // Copy it in control of Nit
-               char *nit_cstr = (char*)malloc(len+1);
-               memcpy(nit_cstr, java_cstr, len);
-               nit_cstr[len] = '\0';
-
-               // Free JNI ref and return
-               (*env)->ReleaseStringUTFChars(env, self, java_cstr);
-               return nit_cstr;
-       `}
-
-       redef fun to_s do return to_cstring.to_s
-end
-
-redef class NativeString
-       # Get a Java string from this C string
-       #
-       # This instance is only valid until the next execution of Java code.
-       # You can use `new_local_ref` to keep it longer.
-       fun to_java_string: JavaString import sys, Sys.jni_env `{
-               Sys sys = JavaString_sys(self);
-               JNIEnv *env = Sys_jni_env(sys);
-               return (*env)->NewStringUTF(env, self);
-       `}
-end
-
-redef class Text
-       # Get `self` as a `JavaString`
-       fun to_java_string: JavaString do return to_cstring.to_java_string
-end
-
-redef extern class JavaObject
-
-       # Returns a global reference to the Java object behind this reference
-       #
-       # You must use a global reference when keeping a Java object
-       # across execution of Java code, per JNI specification.
-       fun new_global_ref: SELF import sys, Sys.jni_env `{
-               Sys sys = JavaObject_sys(self);
-               JNIEnv *env = Sys_jni_env(sys);
-               return (*env)->NewGlobalRef(env, self);
-       `}
-
-       # Delete this global reference
-       fun delete_global_ref import sys, Sys.jni_env `{
-               Sys sys = JavaObject_sys(self);
-               JNIEnv *env = Sys_jni_env(sys);
-               (*env)->DeleteGlobalRef(env, self);
-       `}
-
-       # Delete this local reference
-       fun delete_local_ref import sys, Sys.jni_env `{
-               Sys sys = JavaObject_sys(self);
-               JNIEnv *env = Sys_jni_env(sys);
-               (*env)->DeleteLocalRef(env, self);
-       `}
-
-       # Pops the current local reference frame and return a valid reference to self
-       #
-       # Similar to `JavaVM::pop_local_frame` but returns a value.
-       fun pop_from_local_frame: SELF
-       do
-               var jni_env = sys.jni_env
-               return pop_from_local_frame_with_env(jni_env)
-       end
-
-       private fun pop_from_local_frame_with_env(jni_env: JniEnv): SELF `{
-               return (*jni_env)->PopLocalFrame(jni_env, self);
-       `}
-
-       # Is `self` null in Java?
-       #
-       # Since Java type system doesn't have the same `nullable` concept as Nit's,
-       # the two systems are not directly compatible. Any Nit instances of
-       # `JavaObject` may hold a Java null.
-       #
-       # To benefit from the safer type system of Nit, it is recommended to check
-       # the return of all extern methods implemented in Java to ensure the value
-       # is not a Java null. In case it is, you should replace it by a normal Nit
-       # `null`.
-       fun is_java_null: Bool in "Java" `{ return self == null; `}
-
-       # `JavaString` representation of `self` using Java's `toString`
-       fun to_java_string: JavaString in "Java" `{ return self.toString(); `}
+module java
 
-       # Use Java's `toString` for any `JavaObject`
-       redef fun to_s
-       do
-               if is_java_null then return super
-               return to_java_string.to_s
-       end
-end
+import base
+import collections
index 39938bd..5192797 100644 (file)
@@ -534,6 +534,16 @@ interface MapRead[K, V]
        #     assert x.is_empty  == false
        fun is_empty: Bool is abstract
 
+       # Alias for `not is_empty`.
+       #
+       # Some people prefer to have conditions grammatically easier to read.
+       #
+       #     var map = new HashMap[String, Int]
+       #     assert map.not_empty == false
+       #     map["one"] = 1
+       #     assert map.not_empty == true
+       fun not_empty: Bool do return not self.is_empty
+
        # Number of items in the collection.
        #
        #     var x = new HashMap[String, Int]