java: intro services for JNI local reference frame
[nit.git] / lib / jvm.nit
1 # This file is part of NIT ( http://www.nitlanguage.org ).
2 #
3 # Copyright 2014 Romain Chanoir <romain.chanoir@courrier.uqam.ca>
4 # Copyright 2014 Alexis Laferrière <alexis.laf@xymus.net>
5 #
6 # Licensed under the Apache License, Version 2.0 (the "License");
7 # you may not use this file except in compliance with the License.
8 # You may obtain a copy of the License at
9 #
10 # http://www.apache.org/licenses/LICENSE-2.0
11 #
12 # Unless required by applicable law or agreed to in writing, software
13 # distributed under the License is distributed on an "AS IS" BASIS,
14 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 # See the License for the specific language governing permissions and
16 # limitations under the License.
17
18 # Manipulates the Java Virtual Machine
19 #
20 # See: http://docs.oracle.com/javase/1.5.0/docs/guide/jni/spec/jniTOC.html
21 module jvm is
22 c_compiler_option("-I $(JAVA_HOME)/include/")
23 c_linker_option("-L $(JNI_LIB_PATH) -ljvm")
24 end
25
26 in "C Header" `{
27 #include <jni.h>
28 `}
29
30 # Utility to select options to create the VM using `create_jvm`
31 #
32 # Usage example:
33 # ~~~~
34 # var builder = new JavaVMBuilder
35 # builder.options.add "-Djava.class.path=."
36 # var jvm = builder.create_jvm
37 # var env = builder.jni_env
38 # ~~~~
39 class JavaVMBuilder
40 super JniEnvRef
41
42 var version: Int = "00010002".to_hex
43 var options = new Array[String]
44
45 fun create_jvm: nullable JavaVM
46 do
47 var args = new JavaVMInitArgs
48 args.version = version
49 args.set_default
50 args.n_options = options.length
51
52 var c_options = new JavaVMOptionArray(options.length)
53 for o in options.length.times do
54 var option = options[o]
55 var c_option = c_options[o]
56 c_option.string = option
57 end
58
59 args.options = c_options
60
61 var jvm = new JavaVM(args, self)
62
63 args.free
64 c_options.free
65
66 if jvm.address_is_null then return null
67 return jvm
68 end
69 end
70
71 extern class JavaVMInitArgs `{ JavaVMInitArgs* `}
72 new `{ return (JavaVMInitArgs*)malloc(sizeof(JavaVMInitArgs)); `}
73
74 # Set the defaut config for a VM
75 # Can be called after setting the version
76 #
77 # Unavailable on Android, where you cannot instanciate a new JVM.
78 fun set_default `{
79 #ifndef ANDROID
80 JNI_GetDefaultJavaVMInitArgs(recv);
81 #endif
82 `}
83
84 fun version: Int `{ return recv->version; `}
85 fun version=(v: Int) `{ recv->version = v; `}
86
87 fun options: JavaVMOptionArray `{ return recv->options; `}
88 fun options=(v: JavaVMOptionArray) `{ recv->options = v; `}
89
90 fun n_options: Int `{ return recv->nOptions; `}
91 fun n_options=(v: Int) `{ recv->nOptions = v; `}
92 end
93
94 extern class JavaVMOption `{ JavaVMOption* `}
95 fun string: String import NativeString.to_s `{
96 return NativeString_to_s((char*)recv->optionString);
97 `}
98 fun string=(v: String) import String.to_cstring `{
99 recv->optionString = String_to_cstring(v);
100 `}
101
102 fun extra_info: String import NativeString.to_s `{
103 return NativeString_to_s((char*)recv->extraInfo);
104 `}
105 fun extra_info=(v: String) import String.to_cstring `{
106 recv->extraInfo = String_to_cstring(v);
107 `}
108 end
109
110 extern class JavaVMOptionArray `{ JavaVMOption* `}
111 new(size: Int) `{ return (JavaVMOption*)malloc(sizeof(JavaVMOption)*size); `}
112
113 fun [](i: Int): JavaVMOption `{ return recv+i; `}
114 end
115
116 # Represents a jni JavaVM
117 extern class JavaVM `{JavaVM *`}
118 # Create the JVM, returns its handle and store the a pointer to JniEnv in `env_ref`
119 #
120 # Unavailable on Android, where you cannot instanciate a new JVM.
121 new(args: JavaVMInitArgs, env_ref: JniEnvRef) import jni_error, JniEnvRef.jni_env=, JniEnv.as nullable `{
122
123 #ifdef ANDROID
124 JavaVM_jni_error(NULL, "JVM creation not supported on Android", 0);
125 return NULL;
126 #endif
127
128 JavaVM *jvm;
129 JNIEnv *env;
130 jint res;
131
132 res = JNI_CreateJavaVM(&jvm, (void**)&env, args);
133
134 if (res != 0) {
135 JavaVM_jni_error(NULL, "Could not create Java VM", res);
136 return NULL;
137 }
138 else {
139 JniEnvRef_jni_env__assign(env_ref, JniEnv_as_nullable_JniEnv(env));
140 return jvm;
141 }
142 `}
143
144 fun jni_error(msg: NativeString, v: Int)
145 do
146 print "JNI Error: {msg} ({v})"
147 abort
148 end
149
150 fun destroy `{
151 (*recv)->DestroyJavaVM(recv);
152 `}
153
154 fun env: JniEnv import jni_error `{
155 JNIEnv *env;
156 int res = (*recv)->GetEnv(recv, (void **)&env, JNI_VERSION_1_6);
157 if (res != JNI_OK) {
158 JavaVM_jni_error(NULL, "Could not get JNIEnv from Java VM", res);
159 return NULL;
160 }
161 return env;
162 `}
163
164 fun attach_current_thread: JniEnv `{
165 JNIEnv *env;
166 #ifdef ANDROID
167 // the signature is different (better actually) on Android
168 int res = (*recv)->AttachCurrentThread(recv, &env, NULL);
169 #else
170 int res = (*recv)->AttachCurrentThread(recv, (void**)&env, NULL);
171 #endif
172 if (res != JNI_OK) {
173 JavaVM_jni_error(NULL, "Could not attach current thread to Java VM", res);
174 return NULL;
175 }
176 `}
177 end
178
179 # Represents a jni JNIEnv, which is a thread in a JavaVM
180 extern class JniEnv `{JNIEnv *`}
181
182 # Get a class object from its fully-qualified name or null if the class cannot be found
183 fun find_class(class_name : String): JClass import String.to_cstring `{
184 return (*recv)->FindClass(recv,String_to_cstring(class_name));
185 `}
186
187 # Return the method id for an instance of a class or interface
188 # The method is determined by its name and signature
189 # To obtain the method ID of a constructor, supply "<init>" as the method name and "void(V)" as the return type
190 fun get_method_id(clazz : JClass, name : String, signature : String): JMethodID import String.to_cstring `{
191 return (*recv)->GetMethodID(recv, clazz, String_to_cstring(name), String_to_cstring(signature));
192 `}
193
194 # Construct a new Java object from the `clazz`, using the constructor ̀ method_id`
195 fun new_object(clazz: JClass, method_id: JMethodID): JavaObject `{
196 return (*recv)->NewObject(recv, clazz, method_id);
197 `}
198
199 # Return the JClass of `obj`
200 fun get_object_class(obj: JavaObject): JClass `{
201 return (*recv)->GetObjectClass(recv, obj);
202 `}
203
204 # Registers native methods with the class specified by the `clazz` argument
205 fun register_natives(clazz: JClass, method: JNINativeMethod, n_method : Int): Int `{
206 return (*recv)->RegisterNatives(recv, clazz, method, n_method);
207 `}
208
209 # Call a method on `obj` designed by `method_id` with an array `args` of arguments
210 fun call_void_method(obj: JavaObject, method_id: JMethodID, args: nullable Array[nullable Object]) import convert_args_to_jni `{
211 jvalue * args_tab = JniEnv_convert_args_to_jni(recv, args);
212 (*recv)->CallVoidMethodA(recv, obj, method_id, args_tab);
213 free(args_tab);
214 `}
215
216 # Call a method on `obj` designed by `method_id` with an array `args` of argument returning a JavaObject
217 fun call_object_method(obj: JavaObject, method_id: JMethodID, args: nullable Array[nullable Object]): JavaObject import convert_args_to_jni `{
218 jvalue * args_tab = JniEnv_convert_args_to_jni(recv, args);
219 (*recv)->CallObjectMethod(recv, obj, method_id, args_tab);
220 free(args_tab);
221 `}
222
223 # Call a method on `obj` designed by `method_id` with an array `args` of arguments returning a Bool
224 fun call_boolean_method(obj: JavaObject, method_id: JMethodID, args: nullable Array[nullable Object]): Bool import convert_args_to_jni `{
225 jvalue * args_tab = JniEnv_convert_args_to_jni(recv, args);
226 return (*recv)->CallBooleanMethod(recv, obj, method_id, args_tab);
227 free(args_tab);
228 `}
229
230 # Call a method on `obj` designed by `method_id` with an array `args` of arguments returning a Char
231 fun call_char_method(obj: JavaObject, method_id: JMethodID, args: nullable Array[nullable Object]): Char import convert_args_to_jni `{
232 jvalue * args_tab = JniEnv_convert_args_to_jni(recv, args);
233 return (*recv)->CallCharMethod(recv, obj, method_id, args_tab);
234 free(args_tab);
235 `}
236
237 # Call a method on `obj` designed by `method_id` with an array `args` of arguments returning an Int
238 fun call_int_method(obj: JavaObject, method_id: JMethodID, args: nullable Array[nullable Object]): Int import convert_args_to_jni `{
239 jvalue * args_tab = JniEnv_convert_args_to_jni(recv, args);
240 return (*recv)->CallIntMethod(recv, obj, method_id, args_tab);
241 free(args_tab);
242 `}
243
244 # Call a method on `obj` designed by `method_id` with an array `args` of arguments returning a Float
245 fun call_float_method(obj: JavaObject, method_id: JMethodID, args: nullable Array[nullable Object]): Float import convert_args_to_jni `{
246 jvalue * args_tab = JniEnv_convert_args_to_jni(recv, args);
247 return (*recv)->CallFloatMethod(recv, obj, method_id, args_tab);
248 free(args_tab);
249 `}
250
251 # Call a method on `obj` designed by `method_id` with an array `args` of arguments returning a NativeString
252 fun call_string_method(obj: JavaObject, method_id: JMethodID, args: nullable Array[nullable Object]): NativeString import convert_args_to_jni `{
253 jvalue * args_tab = JniEnv_convert_args_to_jni(recv, args);
254 jobject jobj = (*recv)->CallObjectMethod(recv, obj, method_id, args_tab);
255 free(args_tab);
256 return (char*)(*recv)->GetStringUTFChars(recv, (jstring)jobj, NULL);
257 `}
258
259 private fun convert_args_to_jni(args: nullable Array[nullable Object]): Pointer import Array[nullable Object].as not nullable, Array[nullable Object].[], Array[nullable Object].length, nullable Object.as(Int), nullable Object.as(Char), nullable Object.as(Bool), nullable Object.as(Float), nullable Object.as(JavaObject), nullable Object.as(String), String.to_cstring, String.length `{
260 if(nullable_Array_of_nullable_Object_is_null(args)){
261 return NULL;
262 }
263 Array_of_nullable_Object nit_array = nullable_Array_of_nullable_Object_as_Array_of_nullable_Object(args);
264 int nit_array_length = Array_of_nullable_Object_length(nit_array);
265 int i;
266 jvalue *c_array = malloc(sizeof(jvalue)*(nit_array_length));
267 for (i = 0; i < nit_array_length; i ++) {
268 nullable_Object nullable_obj = Array_of_nullable_Object__index(nit_array, i);
269 if(nullable_Object_is_a_Int(nullable_obj)) {
270 int val = nullable_Object_as_Int(nullable_obj);
271 c_array[i].i = val;
272 } else if (nullable_Object_is_a_Char(nullable_obj)){
273 char val = nullable_Object_as_Char(nullable_obj);
274 c_array[i].c = val;
275 } else if (nullable_Object_is_a_Bool(nullable_obj)){
276 int val = nullable_Object_as_Bool(nullable_obj);
277 c_array[i].z = val;
278 } else if(nullable_Object_is_a_Float(nullable_obj)){
279 float val = nullable_Object_as_Float(nullable_obj);
280 c_array[i].f = val;
281 } else if(nullable_Object_is_a_JavaObject(nullable_obj)){
282 jobject val = nullable_Object_as_JavaObject(nullable_obj);
283 c_array[i].l = val;
284 } else if(nullable_Object_is_a_String(nullable_obj)){
285 String val = nullable_Object_as_String(nullable_obj);
286 char* c = String_to_cstring(val);
287 jstring js = (*recv)->NewStringUTF(recv, c);
288 c_array[i].l = js;
289 } else {
290 fprintf(stderr, "NOT YET SUPPORTED: nit objects are not supported\n");
291 exit(1);
292 }
293 }
294 return c_array;
295 `}
296
297 # Returns the field ID for an instance field of a class. The field is specified by its name and signature
298 fun get_field_id(clazz: JClass, name: String, sign: String): JFieldID import String.to_cstring `{
299 return (*recv)->GetFieldID(recv, clazz, String_to_cstring(name), String_to_cstring(sign));
300 `}
301
302 # returns the value of an instance (nonstatic) field of an object. The field to access is specified by a field ID obtained by calling get_field_id()
303 fun get_object_field(obj: JavaObject, fieldID: JFieldID): JavaObject `{
304 return (*recv)->GetObjectField(recv, obj, fieldID);
305 `}
306
307 fun get_boolean_field(obj: JavaObject, fieldID: JFieldID): Bool `{
308 return (*recv)->GetBooleanField(recv, obj, fieldID);
309 `}
310
311 fun get_char_field(obj: JavaObject, fieldID: JFieldID): Char `{
312 return (*recv)->GetCharField(recv, obj, fieldID);
313 `}
314
315 fun get_int_field(obj: JavaObject, fieldID: JFieldID): Int `{
316 return (*recv)->GetIntField(recv, obj, fieldID);
317 `}
318
319 fun get_float_field(obj: JavaObject, fieldID: JFieldID): Float `{
320 return (*recv)->GetFloatField(recv, obj, fieldID);
321 `}
322
323 fun set_object_field(obj: JavaObject, fieldID: JFieldID, value: JavaObject) `{
324 (*recv)->SetObjectField(recv, obj, fieldID, value);
325 `}
326
327 fun set_boolean_field(obj: JavaObject, fieldID: JFieldID, value: Bool) `{
328 (*recv)->SetBooleanField(recv, obj, fieldID, value);
329 `}
330
331 fun set_char_field(obj: JavaObject, fieldID: JFieldID, value: Char) `{
332 (*recv)->SetCharField(recv, obj, fieldID, value);
333 `}
334
335 fun set_int_field(obj: JavaObject, fieldID: JFieldID, value: Int) `{
336 (*recv)->SetIntField(recv, obj, fieldID, value);
337 `}
338
339 fun set_float_field(obj: JavaObject, fieldID: JFieldID, value: Float) `{
340 (*recv)->SetFloatField(recv, obj, fieldID, value);
341 `}
342
343 # Check for pending exception without creating a local reference to the exception object
344 fun exception_check: Bool `{
345 return (*recv)->ExceptionCheck(recv);
346 `}
347
348 # Construct an exception object from the specified class with the message specified by `message` and causes that exception to be thrown
349 fun throw_new(clazz: JClass, message: String): Int import String.to_cstring `{
350 return (*recv)->ThrowNew(recv, clazz, String_to_cstring(message));
351 `}
352
353 # return the exception if there is one in the process of being thrown, or NULL if no exception is currently being thrown
354 fun exception_occurred: JavaObject `{
355 return (*recv)->ExceptionOccurred(recv);
356 `}
357
358 # prints an exception and backtrace to error channel
359 fun exception_describe `{
360 return (*recv)->ExceptionDescribe(recv);
361 `}
362
363 # clears any exception currently being thrown, has no effect if there is no exception
364 fun exception_clear `{
365 return (*recv)->ExceptionClear(recv);
366 `}
367
368 # Raise a fatal error
369 fun fatal_error(msg: String) import String.to_cstring `{
370 (*recv)->FatalError(recv, String_to_cstring(msg));
371 `}
372
373 # Transform a NIT String into a JavaObject
374 fun string_to_jobject(string: String): JavaObject `{
375 return (*recv)->NewStringUTF(recv, String_to_cstring(string));
376 `}
377
378 # Pushes a local reference frame on the JNI stack
379 fun push_local_frame(capacity: Int): Bool `{
380 return (*recv)->PushLocalFrame(recv, capacity);
381 `}
382
383 # Pops the current local reference frame on the JNI stack
384 fun pop_local_frame `{
385 (*recv)->PopLocalFrame(recv, NULL);
386 `}
387 end
388
389 # used to initialize a JavaVM
390 class JniEnvRef
391 var jni_env: nullable JniEnv = null
392 end
393
394 # Represents a jni jclass
395 extern class JClass `{jclass`}
396 end
397
398 # Represents a jni jmethodID
399 extern class JMethodID `{jmethodID`}
400 end
401
402 # Represens a jni jobject
403 extern class JavaObject `{jobject`}
404 end
405
406 # Represents a jni JNINNativeMethod
407 extern class JNINativeMethod `{ JNINativeMethod* `}
408 fun name: String import NativeString.to_s `{
409 return NativeString_to_s((void*)recv->name);
410 `}
411
412 fun name=(name: String) import String.to_cstring `{
413 recv->name = String_to_cstring(name);
414 `}
415
416 fun signature: String import NativeString.to_s `{
417 return NativeString_to_s((void*)recv->signature);
418 `}
419
420 fun signature=(signature: String) import String.to_cstring `{
421 recv->signature = String_to_cstring(signature);
422 `}
423 end
424
425 # Represents a jni jfieldID
426 extern class JFieldID `{jfieldID`}
427 end
428
429 # Reprents a jni jvalue
430 extern class JValue `{jvalue`}
431
432 fun set_boolean(b: Bool) `{
433 recv.z = b;
434 `}
435
436 fun get_boolean:Bool `{
437 return recv.z;
438 `}
439
440 fun set_char(c: Char)`{
441 recv.c = c;
442 `}
443
444 fun get_char: Char `{
445 return recv.c;
446 `}
447
448 fun set_int(i: Int) `{
449 recv.i = i;
450 `}
451
452 fun get_int: Int `{
453 return recv.i;
454 `}
455
456 fun set_float(f: Float) `{
457 recv.f = f;
458 `}
459
460 fun get_float: Float `{
461 return recv.f;
462 `}
463
464 fun set_jobject(obj: JavaObject) `{
465 recv.l = obj;
466 `}
467
468 fun get_jobject: JavaObject `{
469 return recv.l;
470 `}
471 end
472
473 redef class Int
474 redef fun to_jvalue(env): JValue `{
475 jvalue value;
476 value.i = recv;
477 return value;
478 `}
479 end
480
481 redef class Float
482 redef fun to_jvalue(env): JValue `{
483 jvalue value;
484 value.f = recv;
485 return value;
486 `}
487 end
488
489 redef class Bool
490 redef fun to_jvalue(env): JValue `{
491 jvalue value;
492 value.z = recv;
493 return value;
494 `}
495 end
496
497 redef class NativeString
498 redef fun to_jvalue(env)`{
499 jvalue value;
500 value.l = (*env)->NewStringUTF(env, recv);
501 return value;
502 `}
503 end
504
505 redef class String
506 redef fun to_jvalue(env) do
507 return self.to_cstring.to_jvalue(env)
508 end
509 end
510
511 redef class Object
512 fun to_jvalue(env: JniEnv): JValue is abstract
513 end