Java FFI: intro NitObject for references to Nit objects from Java
[nit.git] / lib / java / ffi_support.nit
1 # This file is part of NIT ( http://www.nitlanguage.org ).
2 #
3 # Licensed under the Apache License, Version 2.0 (the "License");
4 # you may not use this file except in compliance with the License.
5 # You may obtain a copy of the License at
6 #
7 # http://www.apache.org/licenses/LICENSE-2.0
8 #
9 # Unless required by applicable law or agreed to in writing, software
10 # distributed under the License is distributed on an "AS IS" BASIS,
11 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 # See the License for the specific language governing permissions and
13 # limitations under the License.
14
15 # Core supporting services for the FFI with Java
16 #
17 # This module *must* be imported by modules using the Java FFI.
18 # Some might prefer to import the whole `java` package as it provides
19 # other useful services.
20 module ffi_support is
21 cflags "-I $(JAVA_HOME)/include/ -I $(JAVA_HOME)/include/linux/"
22 ldflags "-L $(JNI_LIB_PATH) -ljvm"
23 new_annotation extra_java_files
24 extra_java_files "nit.app.NitObject"
25 end
26
27 import jvm
28
29 redef class Sys
30 private var jvm_cache: nullable JavaVM = null
31 private var jni_env_cache: nullable JniEnv = null
32
33 # Default Java Virtual Machine to use
34 #
35 # Instantiated using `create_default_jvm` if not already set.
36 fun jvm: JavaVM
37 do
38 if jvm_cache == null then create_default_jvm
39 return jvm_cache.as(not null)
40 end
41
42 # Sets the current default Java Virtual Machine (use with `jni_env=`)
43 fun jvm=(jvm: JavaVM) do jvm_cache = jvm
44
45 # Current main `JniEnv`
46 fun jni_env: JniEnv
47 do
48 if jni_env_cache == null then create_default_jvm
49 return jni_env_cache.as(not null)
50 end
51
52 # Sets the current default JNI env (use with `jvm=`)
53 fun jni_env=(jni_env: JniEnv) do jni_env_cache = jni_env
54
55 # Called by `jvm` and `jni_env` to instantiate a Java Virtual Machine.
56 # Used mostly for the FFI with Java.
57 protected fun create_default_jvm
58 do
59 var builder = new JavaVMBuilder
60
61 # By default, look for Java classes in a jar file the same directory as the executable
62 builder.options.add "-Djava.class.path={sys.program_name}.jar"
63
64 var jvm = builder.create_jvm
65 assert jvm != null else print "JVM creation failed"
66
67 self.jvm = jvm
68 assert not jvm.address_is_null
69 self.jni_env = jvm.env
70 assert not jni_env.address_is_null
71 end
72
73 # Get a Java class by its name from the current `jni_env`
74 fun load_jclass(name: CString): JClass import jni_env `{
75 JNIEnv *nit_ffi_jni_env = Sys_jni_env(self);
76
77 // retrieve the implementation Java class
78 jclass java_class = (*nit_ffi_jni_env)->FindClass(nit_ffi_jni_env, name);
79 if (java_class == NULL) {
80 fprintf(stderr, "Nit FFI with Java error: failed to load class.\\n");
81 (*nit_ffi_jni_env)->ExceptionDescribe(nit_ffi_jni_env);
82 exit(1);
83 }
84
85 return java_class;
86 `}
87 end
88
89 # A standard Java string `java.lang.String`
90 #
91 # Converted to a Nit string using `to_s`, or to a C string with `to_cstring`.
92 # Created using `String::to_java_string` or `CString::to_java_string`.
93 extern class JavaString in "Java" `{ java.lang.String `}
94 super JavaObject
95
96 # Get the string from Java and copy it to Nit memory
97 fun to_cstring: CString import sys, Sys.jni_env `{
98 Sys sys = JavaString_sys(self);
99 JNIEnv *env = Sys_jni_env(sys);
100
101 // Get the data from Java
102 const char *java_cstr = (*env)->GetStringUTFChars(env, self, NULL);
103 jsize len = (*env)->GetStringUTFLength(env, self);
104
105 // Copy it in control of Nit
106 char *nit_cstr = (char*)malloc(len+1);
107 memcpy(nit_cstr, java_cstr, len);
108 nit_cstr[len] = '\0';
109
110 // Free JNI ref and return
111 (*env)->ReleaseStringUTFChars(env, self, java_cstr);
112 return nit_cstr;
113 `}
114
115 redef fun to_s
116 do
117 if is_java_null then return "<{inspect_head}:null>"
118 return to_cstring.to_s
119 end
120 end
121
122 redef class CString
123 # Get a Java string from this C string
124 #
125 # This instance is only valid until the next execution of Java code.
126 # You can use `new_local_ref` to keep it longer.
127 fun to_java_string: JavaString import sys, Sys.jni_env `{
128 Sys sys = JavaString_sys(self);
129 JNIEnv *env = Sys_jni_env(sys);
130 return (*env)->NewStringUTF(env, self);
131 `}
132 end
133
134 redef class Text
135 # Get `self` as a `JavaString`
136 fun to_java_string: JavaString do return to_cstring.to_java_string
137 end
138
139 redef extern class JavaObject
140
141 # Returns a global reference to the Java object behind this reference
142 #
143 # You must use a global reference when keeping a Java object
144 # across execution of Java code, per JNI specification.
145 fun new_global_ref: SELF import sys, Sys.jni_env `{
146 Sys sys = JavaObject_sys(self);
147 JNIEnv *env = Sys_jni_env(sys);
148 return (*env)->NewGlobalRef(env, self);
149 `}
150
151 # Delete this global reference
152 fun delete_global_ref import sys, Sys.jni_env `{
153 Sys sys = JavaObject_sys(self);
154 JNIEnv *env = Sys_jni_env(sys);
155 (*env)->DeleteGlobalRef(env, self);
156 `}
157
158 # Delete this local reference
159 fun delete_local_ref import sys, Sys.jni_env `{
160 Sys sys = JavaObject_sys(self);
161 JNIEnv *env = Sys_jni_env(sys);
162 (*env)->DeleteLocalRef(env, self);
163 `}
164
165 # Pops the current local reference frame and return a valid reference to self
166 #
167 # Similar to `JavaVM::pop_local_frame` but returns a value.
168 fun pop_from_local_frame: SELF
169 do
170 var jni_env = sys.jni_env
171 return pop_from_local_frame_with_env(jni_env)
172 end
173
174 # Java implementation of `pop_from_local_frame`
175 protected fun pop_from_local_frame_with_env(jni_env: JniEnv): SELF `{
176 return (*jni_env)->PopLocalFrame(jni_env, self);
177 `}
178
179 # Is `self` null in Java?
180 #
181 # Since Java type system doesn't have the same `nullable` concept as Nit's,
182 # the two systems are not directly compatible. Any Nit instances of
183 # `JavaObject` may hold a Java null.
184 #
185 # To benefit from the safer type system of Nit, it is recommended to check
186 # the return of all extern methods implemented in Java to ensure the value
187 # is not a Java null. In case it is, you should replace it by a normal Nit
188 # `null`.
189 fun is_java_null: Bool in "Java" `{ return self == null; `}
190
191 # `JavaString` representation of `self` using Java's `toString`
192 fun to_java_string: JavaString in "Java" `{ return self.toString(); `}
193
194 # Use Java's `toString` for any `JavaObject`
195 redef fun to_s
196 do
197 if is_java_null then return "<{inspect_head}:null>"
198 return to_java_string.to_s
199 end
200 end
201
202 # Java class: java.lang.Throwable
203 extern class JavaThrowable in "Java" `{ java.lang.Throwable `}
204 super JavaObject
205
206 # Java implementation: java.lang.String java.lang.Throwable.getMessage()
207 fun message: JavaString in "Java" `{
208 return self.getMessage();
209 `}
210
211 # Java implementation: java.lang.String java.lang.Throwable.getLocalizedMessage()
212 fun localized_message: JavaString in "Java" `{
213 return self.getLocalizedMessage();
214 `}
215
216 # Java implementation: java.lang.Throwable.printStackTrace()
217 fun print_stack_trace in "Java" `{
218 self.printStackTrace();
219 `}
220
221 # Java implementation: java.lang.Throwable java.lang.Throwable.getCause()
222 fun cause: JavaThrowable in "Java" `{
223 return self.getCause();
224 `}
225
226 redef fun new_global_ref import sys, Sys.jni_env `{
227 Sys sys = JavaThrowable_sys(self);
228 JNIEnv *env = Sys_jni_env(sys);
229 return (*env)->NewGlobalRef(env, self);
230 `}
231
232 redef fun pop_from_local_frame_with_env(jni_env) `{
233 return (*jni_env)->PopLocalFrame(jni_env, self);
234 `}
235 end
236
237 # Java class: java.lang.Exception
238 extern class JavaException in "Java" `{ java.lang.Exception `}
239 super JavaThrowable
240
241 redef fun new_global_ref import sys, Sys.jni_env `{
242 Sys sys = JavaException_sys(self);
243 JNIEnv *env = Sys_jni_env(sys);
244 return (*env)->NewGlobalRef(env, self);
245 `}
246
247 redef fun pop_from_local_frame_with_env(jni_env) `{
248 return (*jni_env)->PopLocalFrame(jni_env, self);
249 `}
250 end
251
252 redef class JniEnv
253 # Create new `Java.nio.ByteBuffer` referring to `address` with the given `capacity` in bytes
254 #
255 # JNI function: NewDirectByteBuffer
256 fun new_direct_byte_buffer(address: Pointer, capacity: Int): Java_nio_ByteBuffer `{
257 return (*self)->NewDirectByteBuffer(self, address, capacity);
258 `}
259 end
260
261 # Container for data of a specific primitive type
262 #
263 # Java class: java.nio.Buffer
264 extern class Java_nio_Buffer in "Java" `{ java.nio.Buffer `}
265 super JavaObject
266
267 # Address pointed by this buffer
268 #
269 # JNI function: GetDirectBufferAddress
270 fun direct_buffer_address(jni_env: JniEnv): Pointer `{
271 return (*jni_env)->GetDirectBufferAddress(jni_env, self);
272 `}
273
274 # Capacity of this this buffer
275 #
276 # JNI function: GetDirectBufferCapacity
277 fun direct_buffer_capacity(jni_env: JniEnv): Int `{
278 return (*jni_env)->GetDirectBufferCapacity(jni_env, self);
279 `}
280 end
281
282 # A byte buffer
283 #
284 # Java class: java.nio.ByteBuffer
285 extern class Java_nio_ByteBuffer in "Java" `{ java.nio.ByteBuffer `}
286 super Java_nio_Buffer
287
288 # Allocate a new `java.nio.ByteBuffer` with `allocateDirect`
289 new direct(size: Int) in "Java" `{
290 return java.nio.ByteBuffer.allocateDirect((int)size);
291 `}
292 end