Merge: autosuperinit: do not crash on broken model
authorJean Privat <jean@pryen.org>
Mon, 9 Nov 2015 23:29:45 +0000 (18:29 -0500)
committerJean Privat <jean@pryen.org>
Mon, 9 Nov 2015 23:29:45 +0000 (18:29 -0500)
Fix the reason that the catalog is currenlty broken (maybe ironicaly related to #1816)

Pull-Request: #1824

17 files changed:
clib/gc_chooser.c
lib/android/http_request.nit [new file with mode: 0644]
lib/app/http_request.nit [new file with mode: 0644]
lib/core/error.nit
lib/gtk/v3_4/gdk.nit
lib/java/base.nit
lib/json/serialization.nit
lib/jvm.nit
lib/linux/http_request.nit [new file with mode: 0644]
lib/serialization/serialization.nit
src/compiler/abstract_compiler.nit
src/compiler/separate_compiler.nit
src/ffi/java.nit
src/nitserial.nit
src/platform/android.nit
tests/sav/test_ffi_java_refs.res [new file with mode: 0644]
tests/test_ffi_java_refs.nit [new file with mode: 0644]

index 497cc7e..d3854c8 100644 (file)
@@ -18,6 +18,9 @@
 #ifdef ANDROID
        #include <android/log.h>
        #define PRINT_ERROR(...) ((void)__android_log_print(ANDROID_LOG_WARN, "nit", __VA_ARGS__))
+
+       // FIXME bring back when the GC is fixed in Android
+       #undef WITH_LIBGC
 #else
        #define PRINT_ERROR(...) ((void)fprintf(stderr, __VA_ARGS__))
 #endif
diff --git a/lib/android/http_request.nit b/lib/android/http_request.nit
new file mode 100644 (file)
index 0000000..d7427c3
--- /dev/null
@@ -0,0 +1,104 @@
+# 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.
+
+# Android implementation of `app:http_request`
+module http_request is
+       android_manifest """<uses-permission android:name="android.permission.INTERNET" />"""
+end
+
+intrude import app::http_request
+import ui
+
+in "Java" `{
+       import org.apache.http.client.methods.HttpGet;
+       import org.apache.http.impl.client.DefaultHttpClient;
+       import org.apache.http.HttpResponse;
+       import org.apache.http.HttpStatus;
+       import org.apache.http.StatusLine;
+       import java.io.ByteArrayOutputStream;
+`}
+
+redef class App
+       redef fun run_on_ui_thread(task) do app.native_activity.run_on_ui_thread task
+end
+
+redef class Text
+
+       redef fun http_get
+       do
+               jni_env.push_local_frame 8
+               var juri = self.to_java_string
+               var jrep = java_http_get(juri)
+
+               assert not jrep.is_java_null
+
+               var res
+               if jrep.is_exception then
+                       jrep = jrep.as_exception
+                       res = new HttpRequestResult(null, new IOError(jrep.message.to_s))
+               else if jrep.is_http_response then
+                       jrep = jrep.as_http_response
+                       res = new HttpRequestResult(jrep.content.to_s, null, jrep.status_code)
+               else abort
+
+               jni_env.pop_local_frame
+               return res
+       end
+end
+
+redef class AsyncHttpRequest
+
+       redef fun main
+       do
+               var res = super
+               jvm.detach_current_thread
+               return res
+       end
+end
+
+redef class JavaObject
+       private fun is_exception: Bool in "Java" `{ return self instanceof Exception; `}
+       private fun as_exception: JavaException in "Java" `{ return (Exception)self; `}
+
+       private fun is_http_response: Bool in "Java" `{ return self instanceof HttpResponse; `}
+       private fun as_http_response: JavaHttpResponse in "Java" `{ return (HttpResponse)self; `}
+end
+
+private fun java_http_get(uri: JavaString): JavaObject in "Java" `{
+       try {
+               DefaultHttpClient client = new DefaultHttpClient();
+               HttpGet get = new HttpGet(uri);
+               return client.execute(get);
+       } catch (Exception ex) {
+               return ex;
+       }
+`}
+
+private extern class JavaHttpResponse in "Java" `{ org.apache.http.HttpResponse `}
+       super JavaObject
+
+       fun status_code: Int in "Java" `{ return self.getStatusLine().getStatusCode(); `}
+
+       fun content: JavaString in "Java" `{
+               try {
+                       ByteArrayOutputStream out = new ByteArrayOutputStream();
+                       self.getEntity().writeTo(out);
+                       out.close();
+                       return out.toString();
+               } catch (Exception ex) {
+                       ex.printStackTrace();
+                       return "";
+               }
+       `}
+end
diff --git a/lib/app/http_request.nit b/lib/app/http_request.nit
new file mode 100644 (file)
index 0000000..70990a9
--- /dev/null
@@ -0,0 +1,161 @@
+# 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.
+
+# HTTP request services: `AsyncHttpRequest` and `Text::http_get`
+module http_request
+
+import app_base
+import pthreads
+import json::serialization
+
+import linux::http_request is conditional(linux)
+import android::http_request is conditional(android)
+
+redef class App
+       # Platform specific service to execute `task` on the main/UI thread
+       fun run_on_ui_thread(task: Task) is abstract
+end
+
+# Thread executing an HTTP request and deserializing JSON asynchronously
+#
+# This class defines four methods acting on the main/UI thread,
+# they should be implemented as needed:
+# * before
+# * on_load
+# * on_fail
+# * after
+class AsyncHttpRequest
+       super Thread
+
+       # Root URI of the remote server
+       fun rest_server_uri: String is abstract
+
+       # Action, or path, for this request within the `rest_server_uri`
+       fun rest_action: String is abstract
+
+       # Should the response content be deserialized from JSON?
+       var deserialize_json = true is writable
+
+       redef fun start
+       do
+               before
+               super
+       end
+
+       redef fun main
+       do
+               var uri = rest_server_uri / rest_action
+
+               # Execute REST request
+               var rep = uri.http_get
+               if rep.is_error then
+                       app.run_on_ui_thread new RestRunnableOnFail(self, rep.error)
+                       return null
+               end
+
+               if not deserialize_json then
+                       app.run_on_ui_thread new RestRunnableOnLoad(self, rep)
+                       return null
+               end
+
+               # Deserialize
+               var deserializer = new JsonDeserializer(rep.value)
+               var res = deserializer.deserialize
+               if deserializer.errors.not_empty then
+                       app.run_on_ui_thread new RestRunnableOnFail(self, deserializer.errors.first)
+               end
+
+               app.run_on_ui_thread new RestRunnableOnLoad(self, res)
+               return null
+       end
+
+       # Prepare the UI or other parts of the program before executing the REST request
+       fun before do end
+
+       # Invoked when the HTTP request returned valid data
+       #
+       # If `deserialize_json`, the default behavior, this method is invoked only if deserialization was successful.
+       # In this case, `result` may be any deserialized object.
+       #
+       # Otherwise, if `not deserialize_json`, `result` contains the content of the response as a `String`.
+       fun on_load(result: nullable Object) do end
+
+       # Invoked when the HTTP request has failed and no data was received or deserialization failed
+       fun on_fail(error: Error) do print_error "REST request '{rest_action}' failed with: {error}"
+
+       # Complete this request whether it was a success or not
+       fun after do end
+end
+
+redef class Text
+       # Execute an HTTP GET request synchronously at the URI `self`
+       #
+       # ~~~nitish
+       # var response = "http://example.org/".http_get
+       # if response.is_error then
+       #     print_error response.error
+       # else
+       #     print "HTTP status code: {response.code}"
+       #     print response.value
+       # end
+       # ~~~
+       private fun http_get: HttpRequestResult is abstract
+end
+
+# Result of a call to `Text::http_get`
+#
+# Users should first check if `is_error` to use `error`.
+# Otherwise they can use `value` to get the content of the response
+# and `code` for the HTTP status code.
+class HttpRequestResult
+       super MaybeError[String, Error]
+
+       # The HTTP status code, if any
+       var maybe_code: nullable Int
+
+       # The status code
+       # Require: `not is_error`
+       fun code: Int do return maybe_code.as(not null)
+end
+
+private abstract class HttpRequestTask
+       super Task
+
+       # `AsyncHttpRequest` to which send callbacks
+       var sender_thread: AsyncHttpRequest
+end
+
+private class RestRunnableOnLoad
+       super HttpRequestTask
+
+       var res: nullable Object
+
+       redef fun main
+       do
+               sender_thread.on_load(res)
+               sender_thread.after
+       end
+end
+
+private class RestRunnableOnFail
+       super HttpRequestTask
+
+       var error: Error
+
+       redef fun main
+       do
+               sender_thread.on_fail(error)
+               sender_thread.after
+       end
+end
index b1e96e2..ac13301 100644 (file)
@@ -79,7 +79,7 @@ class MaybeError[V, E: Error]
        # REQUIRE: `not is_error`
        fun value: V do return maybe_value.as(V)
 
-       # The require
+       # The error
        # REQUIRE: `is_error`
        fun error: E do return maybe_error.as(E)
 
index 2195ee2..4c2df7b 100644 (file)
@@ -18,26 +18,29 @@ module gdk is pkgconfig "gtk+-3.0"
 import gtk_core
 
 `{
-#ifdef GdkCallback_run
-       // Callback to GdkCallaback::run
-       gboolean nit_gdk_callback(gpointer user_data) {
-               GdkCallback_decr_ref(user_data);
-               return GdkCallback_run(user_data);
+#ifdef Task_gdk_main
+       // Callback to Task::gdk_main
+       gboolean nit_gdk_callback_task(gpointer user_data) {
+               Task_decr_ref(user_data);
+               return Task_gdk_main(user_data);
        }
 #endif
 `}
 
-# Callback to pass to `gdk_threads_add_idle`
-class GdkCallback
+redef class Task
 
        # Small unit of code executed by the GDK loop when idle
        #
-       # Returns true if this object should be invoked again.
-       fun run: Bool do return false
+       # Returns `true` if this object should be invoked again.
+       fun gdk_main: Bool
+       do
+               main
+               return false
+       end
 end
 
 # Add a callback to execute whenever there are no higher priority events pending
-fun gdk_threads_add_idle(callback: GdkCallback): Int import GdkCallback.run `{
-       GdkCallback_incr_ref(callback);
-       return gdk_threads_add_idle(&nit_gdk_callback, callback);
+fun gdk_threads_add_idle(task: Task): Int import Task.gdk_main `{
+       Task_incr_ref(task);
+       return gdk_threads_add_idle(&nit_gdk_callback_task, task);
 `}
index 0db04a4..274533d 100644 (file)
@@ -193,3 +193,53 @@ redef extern class JavaObject
                return to_java_string.to_s
        end
 end
+
+# Java class: java.lang.Throwable
+extern class JavaThrowable in "Java" `{ java.lang.Throwable `}
+       super JavaObject
+
+       # Java implementation: java.lang.String java.lang.Throwable.getMessage()
+       fun message: JavaString in "Java" `{
+               return self.getMessage();
+       `}
+
+       # Java implementation: java.lang.String java.lang.Throwable.getLocalizedMessage()
+       fun localized_message: JavaString in "Java" `{
+               return self.getLocalizedMessage();
+       `}
+
+       # Java implementation:  java.lang.Throwable.printStackTrace()
+       fun print_stack_trace in "Java" `{
+               self.printStackTrace();
+       `}
+
+       # Java implementation: java.lang.Throwable java.lang.Throwable.getCause()
+       fun cause: JavaThrowable in "Java" `{
+               return self.getCause();
+       `}
+
+       redef fun new_global_ref import sys, Sys.jni_env `{
+               Sys sys = JavaThrowable_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
+
+# Java class: java.lang.Exception
+extern class JavaException in "Java" `{ java.lang.Exception `}
+       super JavaThrowable
+
+       redef fun new_global_ref import sys, Sys.jni_env `{
+               Sys sys = JavaException_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
index 3707b19..ad44406 100644 (file)
@@ -448,6 +448,26 @@ class JsonDeserializer
        end
 end
 
+redef class Text
+
+       # Deserialize a `nullable Object` from this JSON formatted string
+       #
+       # Warning: Deserialization errors are reported with `print_error` and
+       # may be returned as a partial object or as `null`.
+       #
+       # This method is not appropriate when errors need to be handled programmatically,
+       # manually use a `JsonDeserializer` in such cases.
+       fun from_json_string: nullable Object
+       do
+               var deserializer = new JsonDeserializer(self)
+               var res = deserializer.deserialize
+               if deserializer.errors.not_empty then
+                       print_error "Deserialization Errors: {deserializer.errors.join(", ")}"
+               end
+               return res
+       end
+end
+
 redef class Serializable
        private fun serialize_to_json(v: JsonSerializer)
        do
@@ -464,6 +484,16 @@ redef class Serializable
                v.stream.write "\}"
        end
 
+       # Serialize this object to a JSON string with metadata for deserialization
+       fun to_json_string: String
+       do
+               var stream = new StringWriter
+               var serializer = new JsonSerializer(stream)
+               serializer.serialize self
+               stream.close
+               return stream.to_s
+       end
+
        # Serialize this object to plain JSON
        #
        # This is a shortcut using `JsonSerializer::plain_json`,
@@ -551,7 +581,7 @@ redef class SimpleCollection[E]
                end
        end
 
-       redef init from_deserializer(v: Deserializer)
+       redef init from_deserializer(v)
        do
                super
                if v isa JsonDeserializer then
@@ -609,8 +639,7 @@ redef class Map[K, V]
                end
        end
 
-       # Instantiate a new `Array` from its serialized representation.
-       redef init from_deserializer(v: Deserializer)
+       redef init from_deserializer(v)
        do
                super
 
index 62cebe1..379e6ec 100644 (file)
@@ -183,6 +183,14 @@ extern class JavaVM `{JavaVM *`}
                }
                return env;
        `}
+
+       # Detach the calling thread from this JVM
+       fun detach_current_thread import jni_error `{
+               int res = (*self)->DetachCurrentThread(self);
+               if (res != JNI_OK) {
+                       JavaVM_jni_error(NULL, "Could not detach current thread to Java VM", res);
+               }
+       `}
 end
 
 # Represents a jni JNIEnv, which is a thread in a JavaVM
diff --git a/lib/linux/http_request.nit b/lib/linux/http_request.nit
new file mode 100644 (file)
index 0000000..bcc458b
--- /dev/null
@@ -0,0 +1,39 @@
+# 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.
+
+# Implementation of `app::http_request` using GDK and Curl
+module http_request
+
+intrude import app::http_request
+private import curl
+private import gtk::gdk
+
+redef class App
+       redef fun run_on_ui_thread(task) do gdk_threads_add_idle task
+end
+
+redef class Text
+       redef fun http_get
+       do
+               var req = new CurlHTTPRequest(to_s)
+               var rep = req.execute
+               if rep isa CurlResponseSuccess then
+                       return new HttpRequestResult(rep.body_str, null, rep.status_code)
+               else
+                       assert rep isa CurlResponseFailed
+                       var error = new IOError(rep.error_msg)
+                       return new HttpRequestResult(null, error)
+               end
+       end
+end
index 7c689b6..5558d66 100644 (file)
@@ -257,3 +257,25 @@ redef class Ref[E]
                v.serialize_attribute("item", first)
        end
 end
+
+redef class Error
+       super Serializable
+
+       redef init from_deserializer(v)
+       do
+               v.notify_of_creation self
+
+               var message = v.deserialize_attribute("message")
+               if not message isa String then message = ""
+               init message
+
+               var cause = v.deserialize_attribute("cause")
+               if cause isa nullable Error then self.cause = cause
+       end
+
+       redef fun core_serialize_to(v)
+       do
+               v.serialize_attribute("message", message)
+               v.serialize_attribute("cause", cause)
+       end
+end
index de68957..821ad31 100644 (file)
@@ -853,12 +853,14 @@ extern void nitni_global_ref_decr( struct nitni_ref *ref );
                        v.add_decl("int main(int argc, char** argv) \{")
                end
 
+               v.add "#ifndef ANDROID"
                v.add("signal(SIGABRT, sig_handler);")
                v.add("signal(SIGFPE, sig_handler);")
                v.add("signal(SIGILL, sig_handler);")
                v.add("signal(SIGINT, sig_handler);")
                v.add("signal(SIGTERM, sig_handler);")
                v.add("signal(SIGSEGV, sig_handler);")
+               v.add "#endif"
                v.add("signal(SIGPIPE, SIG_IGN);")
 
                v.add("glob_argc = argc; glob_argv = argv;")
index f6c483c..4c5fd25 100644 (file)
@@ -1053,7 +1053,7 @@ class SeparateCompiler
                v.add_abort("type null")
                v.add("\}")
                v.add("if({t}->table_size < 0) \{")
-               v.add("PRINT_ERROR(\"Insantiation of a dead type: %s\\n\", {t}->name);")
+               v.add("PRINT_ERROR(\"Instantiation of a dead type: %s\\n\", {t}->name);")
                v.add_abort("type dead")
                v.add("\}")
        end
index eca32ac..1adb9c9 100644 (file)
@@ -220,6 +220,9 @@ redef class MModule
                for cb in callbacks do
                        jni_methods.add_all(cb.jni_methods_declaration(self))
                end
+               for cb in callbacks_used_from_java.types do
+                       jni_methods.add_all(cb.jni_methods_declaration(self))
+               end
 
                var cf = new CFunction("void nit_ffi_with_java_register_natives(JNIEnv* env, jclass jclazz)")
                cf.exprs.add """
@@ -470,6 +473,38 @@ redef class MType
        # Used by `JavaLanguage::compile_extern_method` when calling JNI's `CallStatic*Method`.
        # This strategy is used by JNI to type the return of callbacks to Java.
        private fun jni_signature_alt: String do return "Int"
+
+       redef fun compile_callback_to_java(mmodule, mainmodule, ccu)
+       do
+               var java_file = mmodule.java_file
+               if java_file == null then return
+
+               for variation in ["incr", "decr"] do
+                       var friendly_name = "{mangled_cname}_{variation}_ref"
+
+                       # C
+                       var csignature = "void {mmodule.impl_java_class_name}_{friendly_name}(JNIEnv *env, jclass clazz, jint object)"
+                       var cf = new CFunction("JNIEXPORT {csignature}")
+                       cf.exprs.add "\tnitni_global_ref_{variation}((void*)(long)object);"
+                       ccu.add_non_static_local_function cf
+
+                       # Java
+                       java_file.class_content.add "private native static void {friendly_name}(int object);\n"
+               end
+       end
+
+       redef fun jni_methods_declaration(from_mmodule)
+       do
+               var arr = new Array[String]
+               for variation in ["incr", "decr"] do
+                       var friendly_name = "{mangled_cname}_{variation}_ref"
+                       var jni_format = "(I)V"
+                       var cname = "{from_mmodule.impl_java_class_name}_{friendly_name}"
+                       arr.add """{"{{{friendly_name}}}", "{{{jni_format}}}", {{{cname}}}}"""
+               end
+
+               return arr
+       end
 end
 
 redef class MClassType
index 83b697e..1c24d69 100644 (file)
@@ -218,6 +218,7 @@ redef class Deserializer
                        if mtype isa MGenericType and
                           mtype.is_subtype(m, null, serializable_type) and
                           mtype.is_visible_from(mmodule) and
+                          mtype.mclass.kind == concrete_kind and
                           not compiled_types.has(mtype) then
 
                                compiled_types.add mtype
index 1a6f458..f1d1476 100644 (file)
@@ -36,7 +36,7 @@ class AndroidPlatform
 
        redef fun name do return "android"
 
-       redef fun supports_libgc do return true
+       redef fun supports_libgc do return false
 
        redef fun supports_libunwind do return false
 
@@ -141,7 +141,7 @@ class AndroidToolchain
                ## Generate Application.mk
                dir = "{android_project_root}/jni/"
                """
-APP_ABI := armeabi armeabi-v7a x86 mips
+APP_ABI := armeabi armeabi-v7a x86
 APP_PLATFORM := android-{{{app_target_api}}}
 """.write_to_file "{dir}/Application.mk"
 
diff --git a/tests/sav/test_ffi_java_refs.res b/tests/sav/test_ffi_java_refs.res
new file mode 100644 (file)
index 0000000..422c2b7
--- /dev/null
@@ -0,0 +1,2 @@
+a
+b
diff --git a/tests/test_ffi_java_refs.nit b/tests/test_ffi_java_refs.nit
new file mode 100644 (file)
index 0000000..efab321
--- /dev/null
@@ -0,0 +1,27 @@
+# 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
+
+class A
+       fun foo in "Java" `{
+               A_incr_ref(self);
+               System.out.println("a");
+               A_decr_ref(self);
+               System.out.println("b");
+       `}
+end
+
+var a = new A
+a.foo