diff --git a/docs/source/api.rst b/docs/source/api.rst index a1ce5878..50c5225a 100644 --- a/docs/source/api.rst +++ b/docs/source/api.rst @@ -171,11 +171,15 @@ Reflection classes Reflection functions -------------------- -.. function:: autoclass(name) +.. function:: autoclass(name, include_protected=True, include_private=True) Return a :class:`JavaClass` that represents the class passed from `name`. The name must be written in the format `a.b.c`, not `a/b/c`. + By default, autoclass will include all fields and methods at all levels of + the inheritance hierarchy. Use the `include_protected` and `include_private` + parameters to limit visibility. + >>> from jnius import autoclass >>> autoclass('java.lang.System') @@ -185,6 +189,10 @@ Reflection functions >>> autoclass('android.provider.Settings$Secure') + .. note:: + If a field and a method have the same name, the field will take + precedence. + .. note:: There are sometimes cases when a Java class contains a member that is a Python keyword (such as `from`, `class`, etc). You will need to use @@ -223,7 +231,7 @@ Java class implementation in Python You need to define at minimum the :data:`__javainterfaces__` attribute, and declare java methods with the :func:`java_method` decorator. - .. notes:: + .. note:: Static methods and static fields are not supported. @@ -371,6 +379,34 @@ steps:: 2. $ cd platforms/android-xx/ # Replace xx with your android version 3. $ javap -s -classpath android.jar android.app.Activity # Replace android.app.Activity with any android class whose methods' signature you want to see +Passing Variables: By Reference or By Value +------------------------------------------- + +When Python objects such as `lists` or `bytearrays` are passed to Java Functions, they are converted +to Java arrays. Since Python does not share the same memory space as the JVM, a copy of the data +needs to be made to pass the data. + +Consider that the Java method might change values in the Java array. If the Java method had been +called from another Java method, the other Java method would see the value changes because the +parameters are passed by reference. The two methods share the same memory space. Only one copy of +the array data exists. + +In Pyjnius, Python calls to Java methods simulate pass by reference by copying the variable values +from the JVM back to Python. This extra copying will have a performance impact for large data +structures. To skip the extra copy and pass by value, use the named parameter `pass_by_reference`. + + obj.method(param1, param2, param3, pass_by_reference=False) + +Since Java does not have function named parameters like Python does, they are interpreted by Pyjnius +and are not passed to the Java method. + +In the above example, the `pass_by_reference` parameter will apply to all the parameters. For more +control you can pass a `list` or `tuple` instead. + + obj.method(param1, param2, param3, pass_by_reference=(False, True, False)) + +If the passed `list` or `tuple` is too short, the final value in the series is used for the +remaining parameters. JVM options and the class path ------------------------------ diff --git a/jnius/jnius_conversion.pxi b/jnius/jnius_conversion.pxi index 711165c1..f038b222 100644 --- a/jnius/jnius_conversion.pxi +++ b/jnius/jnius_conversion.pxi @@ -7,11 +7,15 @@ cdef jstringy_arg(argtype): 'Ljava/lang/CharSequence;', 'Ljava/lang/Object;') -cdef void release_args(JNIEnv *j_env, tuple definition_args, jvalue *j_args, args) except *: +cdef void release_args(JNIEnv *j_env, tuple definition_args, pass_by_reference, jvalue *j_args, args) except *: # do the conversion from a Python object to Java from a Java definition cdef JavaObject jo cdef JavaClass jc cdef int index + cdef int last_pass_by_ref_index + + last_pass_by_ref_index = len(pass_by_reference) - 1 + for index, argtype in enumerate(definition_args): py_arg = args[index] if argtype[0] == 'L': @@ -21,11 +25,12 @@ cdef void release_args(JNIEnv *j_env, tuple definition_args, jvalue *j_args, arg jstringy_arg(argtype): j_env[0].DeleteLocalRef(j_env, j_args[index].l) elif argtype[0] == '[': - ret = convert_jarray_to_python(j_env, argtype[1:], j_args[index].l) - try: - args[index][:] = ret - except TypeError: - pass + if pass_by_reference[min(index, last_pass_by_ref_index)] and hasattr(args[index], '__setitem__'): + ret = convert_jarray_to_python(j_env, argtype[1:], j_args[index].l) + try: + args[index][:] = ret + except TypeError: + pass j_env[0].DeleteLocalRef(j_env, j_args[index].l) cdef void populate_args(JNIEnv *j_env, tuple definition_args, jvalue *j_args, args) except *: @@ -533,6 +538,7 @@ cdef jobject convert_pyarray_to_java(JNIEnv *j_env, definition, pyarray) except cdef unsigned char c_tmp cdef jboolean j_boolean cdef jbyte j_byte + cdef const_jbyte* j_bytes cdef jchar j_char cdef jshort j_short cdef jint j_int @@ -546,7 +552,6 @@ cdef jobject convert_pyarray_to_java(JNIEnv *j_env, definition, pyarray) except cdef ByteArray a_bytes - if definition == 'Ljava/lang/Object;' and len(pyarray) > 0: # then the method will accept any array type as param # let's be as precise as we can @@ -585,6 +590,10 @@ cdef jobject convert_pyarray_to_java(JNIEnv *j_env, definition, pyarray) except a_bytes = pyarray j_env[0].SetByteArrayRegion(j_env, ret, 0, array_size, a_bytes._buf) + elif isinstance(pyarray, (bytearray, bytes)): + j_bytes = pyarray + j_env[0].SetByteArrayRegion(j_env, + ret, 0, array_size, j_bytes) else: for i in range(array_size): c_tmp = pyarray[i] diff --git a/jnius/jnius_export_class.pxi b/jnius/jnius_export_class.pxi index dc4fd111..31e7f720 100644 --- a/jnius/jnius_export_class.pxi +++ b/jnius/jnius_export_class.pxi @@ -360,12 +360,16 @@ cdef class JavaClass(object): raise JavaException('Unable to found the constructor' ' for {0}'.format(self.__javaclass__)) + # determine pass by reference choices + pass_by_reference = kwargs.get('pass_by_reference', True) + pass_by_reference = pass_by_reference if isinstance(pass_by_reference, (tuple, list)) else [pass_by_reference] + # create the object j_self = j_env[0].NewObjectA(j_env, self.j_cls, constructor, j_args) # release our arguments - release_args(j_env, d_args, j_args, args_) + release_args(j_env, d_args, pass_by_reference, j_args, args_) check_exception(j_env) if j_self == NULL: @@ -817,7 +821,7 @@ cdef class JavaMethod(object): self.j_self = jc.j_self return self - def __call__(self, *args): + def __call__(self, *args, **kwargs): # argument array to pass to the method cdef jvalue *j_args = NULL cdef tuple d_args = self.definition_args @@ -835,6 +839,10 @@ cdef class JavaMethod(object): self.classname, self.name) ) + # determine pass by reference choices + pass_by_reference = kwargs.get('pass_by_reference', True) + pass_by_reference = pass_by_reference if isinstance(pass_by_reference, (tuple, list)) else [pass_by_reference] + if not self.is_static and j_env == NULL: raise JavaException( 'Cannot call instance method on a un-instanciated class' @@ -856,7 +864,7 @@ cdef class JavaMethod(object): return self.call_staticmethod(j_env, j_args) return self.call_method(j_env, j_args) finally: - release_args(j_env, self.definition_args, j_args, args) + release_args(j_env, self.definition_args, pass_by_reference, j_args, args) finally: if j_args != NULL: @@ -1100,7 +1108,7 @@ cdef class JavaMultipleMethod(object): jm.set_resolve_info(j_env, j_cls, None, name, classname) self.instance_methods[signature] = jm - def __call__(self, *args): + def __call__(self, *args, **kwargs): # try to match our args to a signature cdef JavaMethod jm cdef list scores = [] @@ -1142,7 +1150,7 @@ cdef class JavaMultipleMethod(object): jm = methods[signature] jm.j_self = self.j_self - return jm.__call__(*args) + return jm.__call__(*args, **kwargs) class JavaStaticMethod(JavaMethod): diff --git a/tests/java-src/org/jnius/VariablePassing.java b/tests/java-src/org/jnius/VariablePassing.java new file mode 100644 index 00000000..3047c954 --- /dev/null +++ b/tests/java-src/org/jnius/VariablePassing.java @@ -0,0 +1,47 @@ +package org.jnius; + +public class VariablePassing { + + public VariablePassing() { + + } + + public VariablePassing(int[] numbers) { + squareNumbers(numbers); + } + + public VariablePassing(int[] numbers1, int[] numbers2, int[] numbers3, int[] numbers4) { + squareNumbers(numbers1); + squareNumbers(numbers2); + squareNumbers(numbers3); + squareNumbers(numbers4); + } + + private static void squareNumbers(int[] numbers) { + for (int i = 0; i < numbers.length; i++) { + numbers[i] = i * i; + } + } + + public static void singleParamStatic(int[] numbers) { + squareNumbers(numbers); + } + + public static void multipleParamsStatic(int[] numbers1, int[] numbers2, int[] numbers3, int[] numbers4) { + squareNumbers(numbers1); + squareNumbers(numbers2); + squareNumbers(numbers3); + squareNumbers(numbers4); + } + + public void singleParam(int[] numbers) { + squareNumbers(numbers); + } + + public void multipleParams(int[] numbers1, int[] numbers2, int[] numbers3, int[] numbers4) { + squareNumbers(numbers1); + squareNumbers(numbers2); + squareNumbers(numbers3); + squareNumbers(numbers4); + } +} diff --git a/tests/test_pass_by_reference_or_value.py b/tests/test_pass_by_reference_or_value.py new file mode 100644 index 00000000..b70d36b5 --- /dev/null +++ b/tests/test_pass_by_reference_or_value.py @@ -0,0 +1,191 @@ +from __future__ import print_function +from __future__ import division +from __future__ import absolute_import +import unittest +from jnius import autoclass + +class PassByReferenceOrValueTest(unittest.TestCase): + + def _verify(self, numbers, changed): + for i in range(len(numbers)): + self.assertEqual(numbers[i], i * i if changed else i) + + def _verify_all(self, numbers, changed): + for n, c in zip(numbers, changed): + self._verify(n, c) + + def test_single_param_static(self): + VariablePassing = autoclass('org.jnius.VariablePassing') + + # passed by reference (default), numbers should change + numbers = list(range(10)) + VariablePassing.singleParamStatic(numbers) + self._verify(numbers, True) + + # passed by reference, numbers should change + numbers = list(range(10)) + VariablePassing.singleParamStatic(numbers, pass_by_reference=True) + self._verify(numbers, True) + + # passed by value, numbers should not change + numbers = list(range(10)) + VariablePassing.singleParamStatic(numbers, pass_by_reference=False) + self._verify(numbers, False) + + def test_single_param(self): + VariablePassing = autoclass('org.jnius.VariablePassing') + variablePassing = VariablePassing() + + # passed by reference (default), numbers should change + numbers = list(range(10)) + variablePassing.singleParam(numbers) + self._verify(numbers, True) + + # passed by reference, numbers should change + numbers = list(range(10)) + variablePassing.singleParam(numbers, pass_by_reference=True) + self._verify(numbers, True) + + # passed by value, numbers should not change + numbers = list(range(10)) + variablePassing.singleParam(numbers, pass_by_reference=False) + self._verify(numbers, False) + + def test_multiple_params_static(self): + VariablePassing = autoclass('org.jnius.VariablePassing') + + # passed by reference (default), all numbers should change + numbers = [list(range(10)) for _ in range(4)] + VariablePassing.multipleParamsStatic(*numbers) + self._verify_all(numbers, [True] * 4) + + # passed by reference, all numbers should change + numbers = [list(range(10)) for _ in range(4)] + VariablePassing.multipleParamsStatic(*numbers, pass_by_reference=True) + self._verify_all(numbers, [True] * 4) + + # passed by value, no numbers should change + numbers = [list(range(10)) for _ in range(4)] + VariablePassing.multipleParamsStatic(*numbers, pass_by_reference=False) + self._verify_all(numbers, [False] * 4) + + # only the first set of numbers should change + numbers = [list(range(10)) for _ in range(4)] + VariablePassing.multipleParamsStatic(*numbers, pass_by_reference=[True, False]) + self._verify_all(numbers, [True, False, False, False]) + + # only the first set of numbers should not change + numbers = [list(range(10)) for _ in range(4)] + VariablePassing.multipleParamsStatic(*numbers, pass_by_reference=[False, True]) + self._verify_all(numbers, [False, True, True, True]) + + # only the odd sets of numbers should change + numbers = [list(range(10)) for _ in range(4)] + changed = (True, False, True, False) + VariablePassing.multipleParamsStatic(*numbers, pass_by_reference=changed) + self._verify_all(numbers, changed) + + # only the even sets of numbers should change + numbers = [list(range(10)) for _ in range(4)] + changed = (False, True, False, True) + VariablePassing.multipleParamsStatic(*numbers, pass_by_reference=changed) + self._verify_all(numbers, changed) + + def test_multiple_params(self): + VariablePassing = autoclass('org.jnius.VariablePassing') + variablePassing = VariablePassing() + + # passed by reference (default), all numbers should change + numbers = [list(range(10)) for _ in range(4)] + variablePassing.multipleParams(*numbers) + self._verify_all(numbers, [True] * 4) + + # passed by reference, all numbers should change + numbers = [list(range(10)) for _ in range(4)] + variablePassing.multipleParams(*numbers, pass_by_reference=True) + self._verify_all(numbers, [True] * 4) + + # passed by value, no numbers should change + numbers = [list(range(10)) for _ in range(4)] + variablePassing.multipleParams(*numbers, pass_by_reference=False) + self._verify_all(numbers, [False] * 4) + + # only the first set of numbers should change + numbers = [list(range(10)) for _ in range(4)] + variablePassing.multipleParams(*numbers, pass_by_reference=[True, False]) + self._verify_all(numbers, [True, False, False, False]) + + # only the first set of numbers should not change + numbers = [list(range(10)) for _ in range(4)] + variablePassing.multipleParams(*numbers, pass_by_reference=[False, True]) + self._verify_all(numbers, [False, True, True, True]) + + # only the odd sets of numbers should change + numbers = [list(range(10)) for _ in range(4)] + changed = (True, False, True, False) + variablePassing.multipleParams(*numbers, pass_by_reference=changed) + self._verify_all(numbers, changed) + + # only the even sets of numbers should change + numbers = [list(range(10)) for _ in range(4)] + changed = (False, True, False, True) + variablePassing.multipleParams(*numbers, pass_by_reference=changed) + self._verify_all(numbers, changed) + + def test_contructor_single_param(self): + VariablePassing = autoclass('org.jnius.VariablePassing') + + # passed by reference (default), numbers should change + numbers = list(range(10)) + variablePassing = VariablePassing(numbers) + self._verify(numbers, True) + + # passed by reference, numbers should change + numbers = list(range(10)) + variablePassing = VariablePassing(numbers, pass_by_reference=True) + self._verify(numbers, True) + + # passed by value, numbers should not change + numbers = list(range(10)) + variablePassing = VariablePassing(numbers, pass_by_reference=False) + self._verify(numbers, False) + + def test_contructor_multiple_params(self): + VariablePassing = autoclass('org.jnius.VariablePassing') + + # passed by reference (default), all numbers should change + numbers = [list(range(10)) for _ in range(4)] + variablePassing = VariablePassing(*numbers) + self._verify_all(numbers, [True] * 4) + + # passed by reference, all numbers should change + numbers = [list(range(10)) for _ in range(4)] + variablePassing = VariablePassing(*numbers, pass_by_reference=True) + self._verify_all(numbers, [True] * 4) + + # passed by value, no numbers should change + numbers = [list(range(10)) for _ in range(4)] + variablePassing = VariablePassing(*numbers, pass_by_reference=False) + self._verify_all(numbers, [False] * 4) + + # only the first set of numbers should change + numbers = [list(range(10)) for _ in range(4)] + variablePassing = VariablePassing(*numbers, pass_by_reference=[True, False]) + self._verify_all(numbers, [True, False, False, False]) + + # only the first set of numbers should not change + numbers = [list(range(10)) for _ in range(4)] + variablePassing = VariablePassing(*numbers, pass_by_reference=[False, True]) + self._verify_all(numbers, [False, True, True, True]) + + # only the odd sets of numbers should change + numbers = [list(range(10)) for _ in range(4)] + changed = (True, False, True, False) + variablePassing = VariablePassing(*numbers, pass_by_reference=changed) + self._verify_all(numbers, changed) + + # only the even sets of numbers should change + numbers = [list(range(10)) for _ in range(4)] + changed = (False, True, False, True) + variablePassing = VariablePassing(*numbers, pass_by_reference=changed) + self._verify_all(numbers, changed)