diff --git a/.gitignore b/.gitignore index d4f8b5a..f48aca7 100644 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,6 @@ build .*.swp *.class .idea +*~ +*.so +*.iml diff --git a/.travis.yml b/.travis.yml index 05d6869..bcfec1e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,15 +1,24 @@ language: python + python: - - "2.7" -# command to install dependencies + - 2.7 + - 3.5 + before_install: - sudo apt-get update - sudo apt-get install python-pip openjdk-7-jdk install: - - pip install --upgrade cython --use-mirrors - - make + - pip install --upgrade cython six -# command to run tests script: + - make - make tests + +notifications: + webhooks: + urls: + - http://kivy.org:5000/travisevent + on_success: always + on_failure: always + on_start: always diff --git a/COPYING b/COPYING deleted file mode 100644 index cca7fc2..0000000 --- a/COPYING +++ /dev/null @@ -1,165 +0,0 @@ - GNU LESSER GENERAL PUBLIC LICENSE - Version 3, 29 June 2007 - - Copyright (C) 2007 Free Software Foundation, Inc. - Everyone is permitted to copy and distribute verbatim copies - of this license document, but changing it is not allowed. - - - This version of the GNU Lesser General Public License incorporates -the terms and conditions of version 3 of the GNU General Public -License, supplemented by the additional permissions listed below. - - 0. Additional Definitions. - - As used herein, "this License" refers to version 3 of the GNU Lesser -General Public License, and the "GNU GPL" refers to version 3 of the GNU -General Public License. - - "The Library" refers to a covered work governed by this License, -other than an Application or a Combined Work as defined below. - - An "Application" is any work that makes use of an interface provided -by the Library, but which is not otherwise based on the Library. -Defining a subclass of a class defined by the Library is deemed a mode -of using an interface provided by the Library. - - A "Combined Work" is a work produced by combining or linking an -Application with the Library. The particular version of the Library -with which the Combined Work was made is also called the "Linked -Version". - - The "Minimal Corresponding Source" for a Combined Work means the -Corresponding Source for the Combined Work, excluding any source code -for portions of the Combined Work that, considered in isolation, are -based on the Application, and not on the Linked Version. - - The "Corresponding Application Code" for a Combined Work means the -object code and/or source code for the Application, including any data -and utility programs needed for reproducing the Combined Work from the -Application, but excluding the System Libraries of the Combined Work. - - 1. Exception to Section 3 of the GNU GPL. - - You may convey a covered work under sections 3 and 4 of this License -without being bound by section 3 of the GNU GPL. - - 2. Conveying Modified Versions. - - If you modify a copy of the Library, and, in your modifications, a -facility refers to a function or data to be supplied by an Application -that uses the facility (other than as an argument passed when the -facility is invoked), then you may convey a copy of the modified -version: - - a) under this License, provided that you make a good faith effort to - ensure that, in the event an Application does not supply the - function or data, the facility still operates, and performs - whatever part of its purpose remains meaningful, or - - b) under the GNU GPL, with none of the additional permissions of - this License applicable to that copy. - - 3. Object Code Incorporating Material from Library Header Files. - - The object code form of an Application may incorporate material from -a header file that is part of the Library. You may convey such object -code under terms of your choice, provided that, if the incorporated -material is not limited to numerical parameters, data structure -layouts and accessors, or small macros, inline functions and templates -(ten or fewer lines in length), you do both of the following: - - a) Give prominent notice with each copy of the object code that the - Library is used in it and that the Library and its use are - covered by this License. - - b) Accompany the object code with a copy of the GNU GPL and this license - document. - - 4. Combined Works. - - You may convey a Combined Work under terms of your choice that, -taken together, effectively do not restrict modification of the -portions of the Library contained in the Combined Work and reverse -engineering for debugging such modifications, if you also do each of -the following: - - a) Give prominent notice with each copy of the Combined Work that - the Library is used in it and that the Library and its use are - covered by this License. - - b) Accompany the Combined Work with a copy of the GNU GPL and this license - document. - - c) For a Combined Work that displays copyright notices during - execution, include the copyright notice for the Library among - these notices, as well as a reference directing the user to the - copies of the GNU GPL and this license document. - - d) Do one of the following: - - 0) Convey the Minimal Corresponding Source under the terms of this - License, and the Corresponding Application Code in a form - suitable for, and under terms that permit, the user to - recombine or relink the Application with a modified version of - the Linked Version to produce a modified Combined Work, in the - manner specified by section 6 of the GNU GPL for conveying - Corresponding Source. - - 1) Use a suitable shared library mechanism for linking with the - Library. A suitable mechanism is one that (a) uses at run time - a copy of the Library already present on the user's computer - system, and (b) will operate properly with a modified version - of the Library that is interface-compatible with the Linked - Version. - - e) Provide Installation Information, but only if you would otherwise - be required to provide such information under section 6 of the - GNU GPL, and only to the extent that such information is - necessary to install and execute a modified version of the - Combined Work produced by recombining or relinking the - Application with a modified version of the Linked Version. (If - you use option 4d0, the Installation Information must accompany - the Minimal Corresponding Source and Corresponding Application - Code. If you use option 4d1, you must provide the Installation - Information in the manner specified by section 6 of the GNU GPL - for conveying Corresponding Source.) - - 5. Combined Libraries. - - You may place library facilities that are a work based on the -Library side by side in a single library together with other library -facilities that are not Applications and are not covered by this -License, and convey such a combined library under terms of your -choice, if you do both of the following: - - a) Accompany the combined library with a copy of the same work based - on the Library, uncombined with any other library facilities, - conveyed under the terms of this License. - - b) Give prominent notice with the combined library that part of it - is a work based on the Library, and explaining where to find the - accompanying uncombined form of the same work. - - 6. Revised Versions of the GNU Lesser General Public License. - - The Free Software Foundation may publish revised and/or new versions -of the GNU Lesser General Public License from time to time. Such new -versions will be similar in spirit to the present version, but may -differ in detail to address new problems or concerns. - - Each version is given a distinguishing version number. If the -Library as you received it specifies that a certain numbered version -of the GNU Lesser General Public License "or any later version" -applies to it, you have the option of following the terms and -conditions either of that published version or of any later version -published by the Free Software Foundation. If the Library as you -received it does not specify a version number of the GNU Lesser -General Public License, you may choose any version of the GNU Lesser -General Public License ever published by the Free Software Foundation. - - If the Library as you received it specifies that a proxy can decide -whether future versions of the GNU Lesser General Public License shall -apply, that proxy's public statement of acceptance of any version is -permanent authorization for you to choose that version for the -Library. diff --git a/LICENSE b/LICENSE index f597403..b336a3a 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2010-2013 Kivy Team and other contributors +Copyright (c) 2010-2015 Kivy Team and other contributors Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/Makefile b/Makefile index 5f452dc..7270012 100644 --- a/Makefile +++ b/Makefile @@ -1,17 +1,32 @@ +all: build_ext + .PHONY: build_ext tests +ifdef PYTHON3 +PYTHON=python3 +NOSETESTS=nosetests-3.4 +else +PYTHON=python +NOSETESTS=nosetests +endif + +JAVAC_OPTS=-target 1.6 -source 1.6 +JAVAC=javac $(JAVAC_OPTS) + +ANT=ant + build_ext: - javac jnius/src/org/jnius/NativeInvocationHandler.java - python setup.py build_ext --inplace -f -g + $(ANT) all + $(PYTHON) setup.py build_ext --inplace -f -g + +clean: + $(ANT) clean + rm -rf build jnius/config.pxi html: $(MAKE) -C docs html -tests: build_ext - cd tests && javac org/jnius/HelloWorld.java - cd tests && javac org/jnius/BasicsTest.java - cd tests && javac org/jnius/MultipleMethods.java - cd tests && javac org/jnius/SimpleEnum.java - cd tests && javac org/jnius/InterfaceWithPublicEnum.java - cd tests && javac org/jnius/ClassArgument.java - cd tests && env PYTHONPATH=..:$(PYTHONPATH) nosetests-2.7 -v +# for use in travis; tests whatever you got. +# use PYTHON3=1 to force python3 in other environments. +tests: + (cd tests; env CLASSPATH=../build/test-classes:../build/classes PYTHONPATH=..:$(PYTHONPATH) $(NOSETESTS) -v) diff --git a/README.md b/README.md index 7a7acdf..ccc55c4 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,11 @@ PyJNIus ======= -Python module to access Java class as Python class, using JNI. +A Python module to access Java classes as Python classes using JNI. -(Work in progress.) +PyJNIus is a "Work In Progress". + +[![Build Status](https://travis-ci.org/kivy/pyjnius.svg?branch=master)](https://travis-ci.org/kivy/pyjnius) Quick overview -------------- @@ -26,22 +28,22 @@ hello Usage on desktop ---------------- -You need a java JDK installed (openjdk will do), cython, and make to build it +You need a java JDK installed (OpenJDK will do), Cython and make to build it. make -That's it! you can run the tests with +That's it! You can run the tests using make tests -To make sure everything is running right. +to ensure everything is running correctly. Usage with python-for-android ----------------------------- * Get http://github.com/kivy/python-for-android * Compile a distribution with `-m "pyjnius kivy"` -* Then, you can do this kind of things: +* Then, you can do this kind of thing: ```python from time import sleep @@ -87,9 +89,9 @@ I/python ( 5983): [0.13407529890537262, 9.4235782623291016, 2.2026655673980713] Advanced example ---------------- -When you use autoclass, it will discover all the methods and fields within the object, and resolve it. -For now, it can be better to declare and use only what you need. -The previous example can be done manually: +When you use autoclass, it will discover all the methods and fields of the +object and resolve them. For now, it is better to declare and use only what you +need. The previous example can be done manually as follows: ```python from time import sleep @@ -112,7 +114,41 @@ for x in xrange(20): sleep(.1) ``` -Support/Discussion ------------------- +Support +------- -mailto:pyjnius-dev@googlegroups.com +If you need assistance, you can ask for help on our mailing list: + +* User Group : https://groups.google.com/group/kivy-users +* Email : kivy-users@googlegroups.com + +We also have an IRC channel: + +* Server : irc.freenode.net +* Port : 6667, 6697 (SSL only) +* Channel : #kivy + +Contributing +------------ + +We love pull requests and discussing novel ideas. Check out our +[contribution guide](http://kivy.org/docs/contribute.html) and +feel free to improve PyJNIus. + +The following mailing list and IRC channel are used exclusively for +discussions about developing the Kivy framework and its sister projects: + +* Dev Group : https://groups.google.com/group/kivy-dev +* Email : kivy-dev@googlegroups.com + +IRC channel: + +* Server : irc.freenode.net +* Port : 6667, 6697 (SSL only) +* Channel : #kivy-dev + +License +------- + +PyJNIus is released under the terms of the MIT License. Please refer to the +LICENSE file. diff --git a/build.xml b/build.xml new file mode 100644 index 0000000..5258b15 --- /dev/null +++ b/build.xml @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/source/android.rst b/docs/source/android.rst index 2c069d1..8da6f1b 100644 --- a/docs/source/android.rst +++ b/docs/source/android.rst @@ -3,37 +3,40 @@ Android ======= -Android have a great and extensive API to control the device, your application -etc. Some part of the Android API is accessible directly with Pyjnius, but some -of them requires you to code in Java. +Android has a great and extensive API to control devices, your application +etc. Some parts of the Android API are directly accessible with Pyjnius but +some of them require you to code in Java. Get the DPI ----------- The `DisplayMetrics -`_ contains multiple fields that can return a lot of information about the device's screen:: +`_ +contains multiple fields that can return a lot of information about the device's +screen:: from jnius import autoclass DisplayMetrics = autoclass('android.util.DisplayMetrics') metrics = DisplayMetrics() print 'DPI', metrics.getDeviceDensity() -Note: To access nested classes, use `$` like: -`autoclass('android.provider.MediaStore$Images$Media')`. +.. Note :: + To access nested classes, use `$` like: + `autoclass('android.provider.MediaStore$Images$Media')`. Recording an audio file ----------------------- By looking at the `Audio Capture `_ guide -from Android, you can see the simple step to do for recording an audio file. -Let's do in with Pyjnius:: +for Android, you can see the simple steps for recording an audio file. +Let's do it with Pyjnius:: from jnius import autoclass from time import sleep - # get the needed Java class + # get the needed Java classes MediaRecorder = autoclass('android.media.MediaRecorder') AudioSource = autoclass('android.media.MediaRecorder$AudioSource') OutputFormat = autoclass('android.media.MediaRecorder$OutputFormat') @@ -42,9 +45,9 @@ Let's do in with Pyjnius:: # create out recorder mRecorder = MediaRecorder() mRecorder.setAudioSource(AudioSource.MIC) - mRecorder.setOutputFormat(OutputFormat.THREE_GPP) + mRecorder.setOutputFormat(OutputFormat.THREE_GPP) mRecorder.setOutputFile('/sdcard/testrecorder.3gp') - mRecorder.setAudioEncoder(AudioEncoder.ARM_NB) + mRecorder.setAudioEncoder(AudioEncoder.AMR_NB) mRecorder.prepare() # record 5 seconds @@ -83,16 +86,17 @@ using the Android Media Player too:: mPlayer.release() -Accessing to the Activity -------------------------- +Accessing the Activity +---------------------- -This example will show how to start a new Intent. Be careful, some Intent -require you to setup some parts in the `AndroidManifest.xml`, and have some -actions done within your own Activity. This is out of the scope of Pyjnius, but -we'll show you what is the best approach for playing with it. +This example will show how to start a new Intent. Be careful: some Intents +require you to setup parts in the `AndroidManifest.xml` and have some +actions performed within your Activity. This is out of the scope of Pyjnius but +we'll show you what the best approach is for playing with it. -On Python-for-android project, you can access to the default `PythonActivity`. -Let's see an example that demonstrate the `Intent.ACTION_VIEW`:: +Using the Python-for-android project, you can access the default +`PythonActivity`. Let's look at an example that demonstrates the +`Intent.ACTION_VIEW`:: from jnius import cast from jnius import autoclass @@ -108,9 +112,9 @@ Let's see an example that demonstrate the `Intent.ACTION_VIEW`:: intent.setData(Uri.parse('http://kivy.org')) # PythonActivity.mActivity is the instance of the current Activity - # BUT, startActivity is a method from the Activity class, not our + # BUT, startActivity is a method from the Activity class, not from our # PythonActivity. - # We need to cast our class into an activity, and use it + # We need to cast our class into an activity and use it currentActivity = cast('android.app.Activity', PythonActivity.mActivity) currentActivity.startActivity(intent) @@ -120,12 +124,12 @@ Let's see an example that demonstrate the `Intent.ACTION_VIEW`:: Accelerometer access -------------------- -The accelerometer is a good example that show how you need to wrote a little +The accelerometer is a good example that shows how to write a little Java code that you can access later with Pyjnius. The `SensorManager `_ -lets you access to the device's sensors. To use it, you need to register a +lets you access the device's sensors. To use it, you need to register a `SensorEventListener `_ and overload 2 abstract methods: `onAccuracyChanged` and `onSensorChanged`. @@ -147,7 +151,7 @@ everything needed for accessing the accelerometer:: // Contain the last event we got from the listener static public SensorEvent lastEvent = null; - + // Define a new listener static class AccelListener implements SensorEventListener { public void onSensorChanged(SensorEvent ev) { diff --git a/docs/source/api.rst b/docs/source/api.rst index 959be7c..9f09a54 100644 --- a/docs/source/api.rst +++ b/docs/source/api.rst @@ -293,7 +293,7 @@ All the types for any part of the signature can be one of: * D = represent a java/lang/Double; * V = represent void, available only for the return type -All the types can have the `[]` suffix to design an array. The return type can be `V` or empty. +All the types can have the `[` prefix to design an array. The return type can be `V` or empty. A signature like:: @@ -302,14 +302,18 @@ A signature like:: -> argument 2 is a java.util.List object -> the method doesn't return anything. - (java.util.Collection, java.lang.Object[]); + (java.util.Collection;[java.lang.Object;)V -> argument 1 is a Collection -> argument 2 is an array of Object -> nothing is returned + + ([B)Z + -> argument 1 is a Byte [] + -> a boolean is returned When you implement Java in Python, the signature of the Java method must match. -Java provide a tool named `javap` to get the signature of any java class. For +Java provides a tool named `javap` to get the signature of any java class. For example:: $ javap -s java.util.Iterator @@ -323,3 +327,59 @@ example:: Signature: ()V } + +JVM options and the class path +------------------------------ + +JVM options need to be set before `import jnius` is called, as they cannot be changed after the VM starts up. +To this end, you can:: + + import jnius_config + jnius_config.add_options('-Xrs', '-Xmx4096') + jnius_config.set_classpath('.', '/usr/local/fem/plugins/*') + import jnius + +If a classpath is set with these functions, it overrides any CLASSPATH environment variable. +Multiple options or path entries should be supplied as multiple arguments to the `add_` and `set_` functions. +If no classpath is provided and CLASSPATH is not set, the path defaults to `'.'`. +This functionality is not available on Android. + + +Pyjnius and threads +------------------- + +.. function:: detach() + + Each time you create a native thread in Python and uses Pyjnius, any call to + Pyjnius methods will force attachment of the native thread to the current JVM. + But you must detach it before leaving the thread, and Pyjnius cannot do it for + you. + +Example:: + + import threading + import jnius + + def run(...): + try: + # use pyjnius here + finally: + jnius.detach() + +If you don't, it will crash on dalvik and ART / Android:: + + D/dalvikvm(16696): threadid=12: thread exiting, not yet detached (count=0) + D/dalvikvm(16696): threadid=12: thread exiting, not yet detached (count=1) + E/dalvikvm(16696): threadid=12: native thread exited without detaching + E/dalvikvm(16696): VM aborting + +Or:: + + W/art (21168): Native thread exiting without having called DetachCurrentThread (maybe it's going to use a pthread_key_create destructor?): Thread[16,tid=21293,Native,Thread*=0x4c25c040,peer=0x677eaa70,"Thread-16219"] + F/art (21168): art/runtime/thread.cc:903] Native thread exited without calling DetachCurrentThread: Thread[16,tid=21293,Native,Thread*=0x4c25c040,peer=0x677eaa70,"Thread-16219"] + F/art (21168): art/runtime/runtime.cc:203] Runtime aborting... + F/art (21168): art/runtime/runtime.cc:203] (Aborting thread was not attached to runtime!) + F/art (21168): art/runtime/runtime.cc:203] Dumping all threads without appropriate locks held: thread list lock mutator lock + F/art (21168): art/runtime/runtime.cc:203] All threads: + F/art (21168): art/runtime/runtime.cc:203] DALVIK THREADS (16): + ... diff --git a/jnius/__init__.py b/jnius/__init__.py index 06435a0..34c2599 100644 --- a/jnius/__init__.py +++ b/jnius/__init__.py @@ -11,3 +11,28 @@ __version__ = '1.1-dev' from .jnius import * from .reflect import * + +# XXX monkey patch methods that cannot be in cython. +# Cython doesn't allow to set new attribute on methods it compiled + +HASHCODE_MAX = 2 ** 31 - 1 + +class PythonJavaClass_(PythonJavaClass): + + @java_method('()I', name='hashCode') + def hashCode(self): + return id(self) % HASHCODE_MAX + + @java_method('()Ljava/lang/String;', name='hashCode') + def hashCode_(self): + return '{}'.format(self.hashCode()) + + @java_method('()Ljava/lang/String;', name='toString') + def toString(self): + return repr(self) + + @java_method('(Ljava/lang/Object;)Z', name='equals') + def equals(self, other): + return self.hashCode() == other.hashCode() + +PythonJavaClass = PythonJavaClass_ diff --git a/jnius/jni.pxi b/jnius/jni.pxi index afcd661..4e6c546 100644 --- a/jnius/jni.pxi +++ b/jnius/jni.pxi @@ -401,4 +401,5 @@ cdef extern from "jni.h": ctypedef struct JNIInvokeInterface: jint (*AttachCurrentThread)(JavaVM *, JNIEnv **, void *) + jint (*DetachCurrentThread)(JavaVM *) diff --git a/jnius/jnius.pyx b/jnius/jnius.pyx index 08f619a..41571bf 100644 --- a/jnius/jnius.pyx +++ b/jnius/jnius.pyx @@ -87,7 +87,7 @@ Python:: __all__ = ('JavaObject', 'JavaClass', 'JavaMethod', 'JavaField', 'MetaJavaClass', 'JavaException', 'cast', 'find_javaclass', - 'PythonJavaClass', 'java_method') + 'PythonJavaClass', 'java_method', 'detach') from libc.stdlib cimport malloc, free from functools import partial @@ -99,14 +99,19 @@ include "config.pxi" IF JNIUS_PLATFORM == "android": include "jnius_jvm_android.pxi" -ELSE: +ELIF JNIUS_PLATFORM == "win32": include "jnius_jvm_desktop.pxi" +ELSE: + include "jnius_jvm_dlopen.pxi" include "jnius_env.pxi" include "jnius_utils.pxi" include "jnius_conversion.pxi" include "jnius_localref.pxi" -include "jnius_nativetypes.pxi" +IF JNIUS_PYTHON3: + include "jnius_nativetypes3.pxi" +ELSE: + include "jnius_nativetypes.pxi" include "jnius_export_func.pxi" include "jnius_export_class.pxi" diff --git a/jnius/jnius_conversion.pxi b/jnius/jnius_conversion.pxi index 35f3807..b276b55 100644 --- a/jnius/jnius_conversion.pxi +++ b/jnius/jnius_conversion.pxi @@ -1,3 +1,10 @@ +from cpython.version cimport PY_MAJOR_VERSION + +cdef jstringy_arg(argtype): + return argtype in ('Ljava/lang/String;', + 'Ljava/lang/CharSequence;', + 'Ljava/lang/Object;') + cdef void release_args(JNIEnv *j_env, tuple definition_args, jvalue *j_args, args) except *: # do the conversion from a Python object to Java from a Java definition cdef JavaObject jo @@ -9,7 +16,7 @@ cdef void release_args(JNIEnv *j_env, tuple definition_args, jvalue *j_args, arg if py_arg is None: j_args[index].l = NULL if isinstance(py_arg, basestring) and \ - argtype in ('Ljava/lang/String;', 'Ljava/lang/Object;'): + 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) @@ -19,7 +26,6 @@ cdef void release_args(JNIEnv *j_env, tuple definition_args, jvalue *j_args, arg 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 *: # do the conversion from a Python object to Java from a Java definition cdef JavaClassStorage jcs @@ -27,6 +33,7 @@ cdef void populate_args(JNIEnv *j_env, tuple definition_args, jvalue *j_args, ar cdef JavaClass jc cdef PythonJavaClass pc cdef int index + cdef bytes py_str for index, argtype in enumerate(definition_args): py_arg = args[index] if argtype == 'Z': @@ -48,10 +55,10 @@ cdef void populate_args(JNIEnv *j_env, tuple definition_args, jvalue *j_args, ar elif argtype[0] == 'L': if py_arg is None: j_args[index].l = NULL - elif isinstance(py_arg, basestring) and \ - argtype in ('Ljava/lang/String;', 'Ljava/lang/Object;'): - j_args[index].l = j_env[0].NewStringUTF( - j_env, py_arg.encode('utf-8')) + elif (isinstance(py_arg, basestring) or (PY_MAJOR_VERSION >=3 and isinstance(py_arg, str))) \ + and jstringy_arg(argtype): + py_str = py_arg.encode('utf-8') + j_args[index].l = j_env[0].NewStringUTF(j_env, py_str) elif isinstance(py_arg, JavaClass): jc = py_arg check_assignable_from(j_env, jc, argtype[1:-1]) @@ -82,28 +89,30 @@ cdef void populate_args(JNIEnv *j_env, tuple definition_args, jvalue *j_args, ar if py_arg is None: j_args[index].l = NULL continue - if isinstance(py_arg, basestring): + if isinstance(py_arg, basestring) and PY_MAJOR_VERSION < 3: if argtype == '[B': py_arg = map(ord, py_arg) elif argtype == '[C': py_arg = list(py_arg) + if isinstance(py_arg, str) and PY_MAJOR_VERSION >= 3 and argtype == '[C': + py_arg = list(py_arg) if isinstance(py_arg, ByteArray) and argtype != '[B': raise JavaException( 'Cannot use ByteArray for signature {}'.format(argtype)) - if not isinstance(py_arg, (list, tuple, ByteArray)): + if not isinstance(py_arg, (list, tuple, ByteArray, bytes)): raise JavaException('Expecting a python list/tuple, got ' '{0!r}'.format(py_arg)) j_args[index].l = convert_pyarray_to_java( j_env, argtype[1:], py_arg) -cdef convert_jobject_to_python(JNIEnv *j_env, bytes definition, jobject j_object): +cdef convert_jobject_to_python(JNIEnv *j_env, definition, jobject j_object): # Convert a Java Object to a Python object, according to the definition. # If the definition is a java/lang/Object, then try to determine what is it # exactly. cdef char *c_str cdef bytes py_str - cdef bytes r = definition[1:-1] + r = definition[1:-1] cdef JavaObject ret_jobject cdef JavaClass ret_jc cdef jclass retclass @@ -112,6 +121,7 @@ cdef convert_jobject_to_python(JNIEnv *j_env, bytes definition, jobject j_object # we got a generic object -> lookup for the real name instead. if r == 'java/lang/Object': r = definition = lookup_java_object_name(j_env, j_object) + print('cjtp:r {0} definition {1}'.format(r, definition)) if definition[0] == '[': return convert_jarray_to_python(j_env, definition[1:], j_object) @@ -122,11 +132,14 @@ cdef convert_jobject_to_python(JNIEnv *j_env, bytes definition, jobject j_object # Ie, B would be passed as Ljava/lang/Character; # if we got a string, just convert back to Python str. - if r == 'java/lang/String': + if r in ('java/lang/String', 'java/lang/CharSequence'): c_str = j_env[0].GetStringUTFChars(j_env, j_object, NULL) py_str = c_str j_env[0].ReleaseStringUTFChars(j_env, j_object, c_str) - return py_str + if PY_MAJOR_VERSION < 3: + return py_str + else: + return py_str.decode('utf-8') # XXX should be deactivable from configuration # ie, user might not want autoconvertion of lang classes. @@ -171,7 +184,7 @@ cdef convert_jobject_to_python(JNIEnv *j_env, bytes definition, jobject j_object from .reflect import Object ret_jc = Object(noinstance=True) else: - from reflect import autoclass + from .reflect import autoclass ret_jc = autoclass(r.replace('/', '.'))(noinstance=True) else: ret_jc = jclass_register[r](noinstance=True) @@ -274,6 +287,20 @@ cdef convert_jarray_to_python(JNIEnv *j_env, definition, jobject j_object): obj = convert_jobject_to_python(j_env, definition, j_object_item) ret.append(obj) j_env[0].DeleteLocalRef(j_env, j_object_item) + + elif r == '[': + r = definition[1:] + ret = [] + for i in range(array_size): + j_object_item = j_env[0].GetObjectArrayElement( + j_env, j_object, i) + if j_object_item == NULL: + ret.append(None) + continue + obj = convert_jarray_to_python(j_env, r, j_object_item) + ret.append(obj) + j_env[0].DeleteLocalRef(j_env, j_object_item) + else: raise JavaException('Invalid return definition for array') @@ -295,9 +322,11 @@ cdef jobject convert_python_to_jobject(JNIEnv *j_env, definition, obj) except *: elif definition[0] == 'L': if obj is None: return NULL - elif isinstance(obj, basestring) and \ - definition in ('Ljava/lang/String;', 'Ljava/lang/Object;'): + elif isinstance(obj, basestring) and jstringy_arg(definition): return j_env[0].NewStringUTF(j_env, obj) + elif isinstance(obj, str) and PY_MAJOR_VERSION >= 3 and jstringy_arg(definition): + utf8 = obj.encode('utf-8') + return j_env[0].NewStringUTF(j_env, utf8) elif isinstance(obj, (int, long)) and \ definition in ( 'Ljava/lang/Integer;', @@ -337,13 +366,23 @@ cdef jobject convert_python_to_jobject(JNIEnv *j_env, definition, obj) except *: definition[1:-1], obj)) elif definition[0] == '[': - conversions = { - int: 'I', - bool: 'Z', - long: 'J', - float: 'F', - basestring: 'Ljava/lang/String;', - } + if PY_MAJOR_VERSION < 3: + conversions = { + int: 'I', + bool: 'Z', + long: 'J', + float: 'F', + basestring: 'Ljava/lang/String;', + } + else: + conversions = { + int: 'I', + bool: 'Z', + long: 'J', + float: 'F', + str: 'Ljava/lang/String;', + bytes: 'B' + } retclass = j_env[0].FindClass(j_env, 'java/lang/Object') retobject = j_env[0].NewObjectArray(j_env, len(obj), retclass, NULL) for index, item in enumerate(obj): @@ -399,6 +438,7 @@ cdef jobject convert_pyarray_to_java(JNIEnv *j_env, definition, pyarray) except cdef jobject ret = NULL cdef int array_size = len(pyarray) cdef int i + cdef unsigned char c_tmp cdef jboolean j_boolean cdef jbyte j_byte cdef jchar j_char @@ -418,13 +458,23 @@ cdef jobject convert_pyarray_to_java(JNIEnv *j_env, definition, pyarray) except 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 - conversions = { - int: 'I', - bool: 'Z', - long: 'J', - float: 'F', - basestring: 'Ljava/lang/String;', - } + if PY_MAJOR_VERSION < 3: + conversions = { + int: 'I', + bool: 'Z', + long: 'J', + float: 'F', + basestring: 'Ljava/lang/String;', + } + else: + conversions = { + int: 'I', + bool: 'Z', + long: 'J', + float: 'F', + bytes: 'B', + str: 'Ljava/lang/String;', + } for _type, override in conversions.iteritems(): if isinstance(pyarray[0], _type): definition = override @@ -445,7 +495,8 @@ cdef jobject convert_pyarray_to_java(JNIEnv *j_env, definition, pyarray) except ret, 0, array_size, a_bytes._buf) else: for i in range(array_size): - j_byte = pyarray[i] + c_tmp = pyarray[i] + j_byte = c_tmp j_env[0].SetByteArrayRegion(j_env, ret, i, 1, &j_byte) @@ -492,8 +543,9 @@ cdef jobject convert_pyarray_to_java(JNIEnv *j_env, definition, pyarray) except ret, i, 1, &j_double) elif definition[0] == 'L': + defstr = str_for_c(definition[1:-1]) j_class = j_env[0].FindClass( - j_env, definition[1:-1]) + j_env, defstr) if j_class == NULL: raise JavaException('Cannot create array with a class not ' 'found {0!r}'.format(definition[1:-1])) @@ -504,12 +556,19 @@ cdef jobject convert_pyarray_to_java(JNIEnv *j_env, definition, pyarray) except if arg is None: j_env[0].SetObjectArrayElement( j_env, ret, i, NULL) - elif isinstance(arg, basestring) and \ - definition in ('Ljava/lang/String;', 'Ljava/lang/Object;'): + elif isinstance(arg, basestring) and PY_MAJOR_VERSION < 3 and \ + jstringy_arg(definition): j_string = j_env[0].NewStringUTF( j_env, arg) j_env[0].SetObjectArrayElement( j_env, ret, i, j_string) + elif isinstance(arg, str) and PY_MAJOR_VERSION >= 3 and \ + jstringy_arg(definition): + utf8 = arg.encode('utf-8') + j_string = j_env[0].NewStringUTF( + j_env, utf8) + j_env[0].SetObjectArrayElement( + j_env, ret, i, j_string) elif isinstance(arg, JavaClass): jc = arg check_assignable_from(j_env, jc, definition[1:-1]) @@ -526,6 +585,17 @@ cdef jobject convert_pyarray_to_java(JNIEnv *j_env, definition, pyarray) except else: raise JavaException('Invalid variable used for L array', definition, pyarray) + elif definition[0] == '[': + subdef = definition[1:] + eproto = convert_pyarray_to_java(j_env, subdef, pyarray[0]) + ret = j_env[0].NewObjectArray( + j_env, array_size, j_env[0].GetObjectClass(j_env, eproto), NULL) + j_env[0].SetObjectArrayElement( + j_env, ret, 0, eproto) + for i in range(1, array_size): + j_env[0].SetObjectArrayElement( + j_env, ret, i, convert_pyarray_to_java(j_env, subdef, pyarray[i])) + else: raise JavaException('Invalid array definition', definition, pyarray) diff --git a/jnius/jnius_env.pxi b/jnius/jnius_env.pxi index 40da529..336ab0e 100644 --- a/jnius/jnius_env.pxi +++ b/jnius/jnius_env.pxi @@ -4,11 +4,13 @@ cdef JNIEnv *default_env = NULL cdef extern int gettid() cdef JavaVM *jvm = NULL -cdef JNIEnv *get_jnienv(): +cdef JNIEnv *get_jnienv() except NULL: global default_env # first call, init. if default_env == NULL: default_env = get_platform_jnienv() + if default_env == NULL: + return NULL default_env[0].GetJavaVM(default_env, &jvm) # return the current env attached to the thread @@ -17,3 +19,7 @@ cdef JNIEnv *get_jnienv(): jvm[0].AttachCurrentThread(jvm, &env, NULL) return env + +def detach(): + jvm[0].DetachCurrentThread(jvm) + diff --git a/jnius/jnius_export_class.pxi b/jnius/jnius_export_class.pxi index 0e321d9..00242de 100644 --- a/jnius/jnius_export_class.pxi +++ b/jnius/jnius_export_class.pxi @@ -1,8 +1,15 @@ - class JavaException(Exception): '''Can be a real java exception, or just an exception from the wrapper. ''' - pass + classname = None # The classname of the exception + innermessage = None # The message of the inner exception + stacktrace = None # The stack trace of the inner exception + + def __init__(self, message, classname=None, innermessage=None, stacktrace=None): + self.classname = classname + self.innermessage = innermessage + self.stacktrace = stacktrace + Exception.__init__(self, message) cdef class JavaObject(object): @@ -34,7 +41,7 @@ cdef dict jclass_register = {} class MetaJavaClass(type): def __new__(meta, classname, bases, classDict): meta.resolve_class(classDict) - tp = type.__new__(meta, classname, bases, classDict) + tp = type.__new__(meta, str(classname), bases, classDict) jclass_register[classDict['__javaclass__']] = tp return tp @@ -73,19 +80,22 @@ class MetaJavaClass(type): with nogil: classLoader = j_env[0].CallStaticObjectMethodA( - j_env, baseclass, getClassLoader, []) + j_env, baseclass, getClassLoader, NULL) jargs = malloc(sizeof(jobject) * 2) jargs[0] = classLoader jargs[1] = interfaces jcs.j_cls = j_env[0].CallStaticObjectMethod( j_env, baseclass, getProxyClass, jargs) + j_env[0].DeleteLocalRef(j_env, baseclass) + if jcs.j_cls == NULL: raise JavaException('Unable to create the class' ' {0}'.format(__javaclass__)) else: + class_name = str_for_c(__javaclass__) jcs.j_cls = j_env[0].FindClass(j_env, - __javaclass__) + class_name) if jcs.j_cls == NULL: raise JavaException('Unable to find the class' ' {0}'.format(__javaclass__)) @@ -100,29 +110,29 @@ class MetaJavaClass(type): # search all the static JavaMethod within our class, and resolve them cdef JavaMethod jm cdef JavaMultipleMethod jmm - for name, value in classDict.iteritems(): + for name, value in items_compat(classDict): if isinstance(value, JavaMethod): jm = value if not jm.is_static: continue jm.set_resolve_info(j_env, jcs.j_cls, None, - name, __javaclass__) + str_for_c(name), str_for_c(__javaclass__)) elif isinstance(value, JavaMultipleMethod): jmm = value jmm.set_resolve_info(j_env, jcs.j_cls, None, - name, __javaclass__) + str_for_c(name), str_for_c(__javaclass__)) # search all the static JavaField within our class, and resolve them cdef JavaField jf - for name, value in classDict.iteritems(): + for name, value in items_compat(classDict): if not isinstance(value, JavaField): continue jf = value if not jf.is_static: continue jf.set_resolve_info(j_env, jcs.j_cls, - name, __javaclass__) + str_for_c(name), str_for_c(__javaclass__)) cdef class JavaClass(object): @@ -209,8 +219,9 @@ cdef class JavaClass(object): populate_args(j_env, d_args, j_args, args_) # get the java constructor + defstr = str_for_c(definition) constructor = j_env[0].GetMethodID( - j_env, self.j_cls, '', definition) + j_env, self.j_cls, '', defstr) if constructor == NULL: raise JavaException('Unable to found the constructor' ' for {0}'.format(self.__javaclass__)) @@ -238,23 +249,23 @@ cdef class JavaClass(object): cdef JavaMethod jm cdef JavaMultipleMethod jmm cdef JNIEnv *j_env = get_jnienv() - for name, value in self.__class__.__dict__.iteritems(): + for name, value in items_compat(self.__class__.__dict__): if isinstance(value, JavaMethod): jm = value if jm.is_static: continue jm.set_resolve_info(j_env, self.j_cls, self.j_self, - name, self.__javaclass__) + str_for_c(name), str_for_c(self.__javaclass__)) elif isinstance(value, JavaMultipleMethod): jmm = value jmm.set_resolve_info(j_env, self.j_cls, self.j_self, - name, self.__javaclass__) + str_for_c(name), str_for_c(self.__javaclass__)) cdef void resolve_fields(self) except *: # search all the JavaField within our class, and resolve them cdef JavaField jf cdef JNIEnv *j_env = get_jnienv() - for name, value in self.__class__.__dict__.iteritems(): + for name, value in items_compat(self.__class__.__dict__): if not isinstance(value, JavaField): continue jf = value @@ -275,10 +286,10 @@ cdef class JavaField(object): cdef jfieldID j_field cdef JNIEnv *j_env cdef jclass j_cls - cdef bytes definition cdef object is_static - cdef bytes name - cdef bytes classname + cdef name + cdef classname + cdef definition def __cinit__(self, definition, **kwargs): self.j_field = NULL @@ -290,7 +301,7 @@ cdef class JavaField(object): self.is_static = kwargs.get('static', False) cdef void set_resolve_info(self, JNIEnv *j_env, jclass j_cls, - bytes name, bytes classname): + name, classname): j_env = get_jnienv() self.name = name self.classname = classname @@ -301,13 +312,16 @@ cdef class JavaField(object): if self.j_field != NULL: return if self.is_static: + defstr = str_for_c(self.definition) self.j_field = j_env[0].GetStaticFieldID( j_env, self.j_cls, self.name, - self.definition) + defstr) else: + defstr = str_for_c(self.definition) + namestr = str_for_c(self.name) self.j_field = j_env[0].GetFieldID( - j_env, self.j_cls, self.name, - self.definition) + j_env, self.j_cls, namestr, + defstr) if self.j_field == NULL: raise JavaException('Unable to found the field {0}'.format(self.name)) @@ -321,6 +335,66 @@ cdef class JavaField(object): j_self = (obj).j_self.obj return self.read_field(j_self) + def __set__(self, obj, value): + cdef jobject j_self + + self.ensure_field() + if obj is None: + # set not implemented for static fields + raise NotImplementedError() + + j_self = (obj).j_self.obj + self.write_field(j_self, value) + + cdef write_field(self, jobject j_self, value): + cdef jboolean j_boolean + cdef jbyte j_byte + cdef jchar j_char + cdef jshort j_short + cdef jint j_int + cdef jlong j_long + cdef jfloat j_float + cdef jdouble j_double + cdef jobject j_object + cdef JNIEnv *j_env = get_jnienv() + + # type of the java field + r = self.definition[0] + + # set the java field; implemented only for primitive types + if r == 'Z': + j_boolean = value + j_env[0].SetBooleanField(j_env, j_self, self.j_field, j_boolean) + elif r == 'B': + j_byte = value + j_env[0].SetByteField(j_env, j_self, self.j_field, j_byte) + elif r == 'C': + j_char = value + j_env[0].SetCharField(j_env, j_self, self.j_field, j_char) + elif r == 'S': + j_short = value + j_env[0].SetShortField(j_env, j_self, self.j_field, j_short) + elif r == 'I': + j_int = value + j_env[0].SetIntField(j_env, j_self, self.j_field, j_int) + elif r == 'J': + j_long = value + j_env[0].SetLongField(j_env, j_self, self.j_field, j_long) + elif r == 'F': + j_float = value + j_env[0].SetFloatField(j_env, j_self, self.j_field, j_float) + elif r == 'D': + j_double = value + j_env[0].SetDoubleField(j_env, j_self, self.j_field, j_double) + elif r == 'L': + j_object = convert_python_to_jobject(j_env, self.definition, value) + j_env[0].SetObjectField(j_env, j_self, self.j_field, j_object) + j_env[0].DeleteLocalRef(j_env, j_object) + else: + raise Exception('Invalid field definition') + + check_exception(j_env) + cdef read_field(self, jobject j_self): cdef jboolean j_boolean cdef jbyte j_byte @@ -474,9 +548,9 @@ cdef class JavaMethod(object): cdef jmethodID j_method cdef jclass j_cls cdef LocalRef j_self - cdef bytes name - cdef bytes classname - cdef bytes definition + cdef name + cdef classname + cdef definition cdef object is_static cdef bint is_varargs cdef object definition_return @@ -489,7 +563,7 @@ cdef class JavaMethod(object): def __init__(self, definition, **kwargs): super(JavaMethod, self).__init__() - self.definition = definition + self.definition = definition self.definition_return, self.definition_args = \ parse_definition(definition) self.is_static = kwargs.get('static', False) @@ -502,20 +576,22 @@ cdef class JavaMethod(object): if self.name is None: raise JavaException('Unable to find a None method!') if self.is_static: + defstr = str_for_c(self.definition) self.j_method = j_env[0].GetStaticMethodID( j_env, self.j_cls, self.name, - self.definition) + defstr) else: + defstr = str_for_c(self.definition) self.j_method = j_env[0].GetMethodID( j_env, self.j_cls, self.name, - self.definition) + defstr) if self.j_method == NULL: raise JavaException('Unable to find the method' ' {0}({1})'.format(self.name, self.definition)) cdef void set_resolve_info(self, JNIEnv *j_env, jclass j_cls, - LocalRef j_self, bytes name, bytes classname): + LocalRef j_self, name, classname): self.name = name self.classname = classname self.j_cls = j_cls @@ -807,7 +883,7 @@ cdef class JavaMultipleMethod(object): else: methods = self.static_methods - for signature, jm in methods.iteritems(): + for signature, jm in items_compat(methods): sign_ret, sign_args = jm.definition_return, jm.definition_args if jm.is_varargs: args_ = args[:len(sign_args) - 1] + (args[len(sign_args) - 1:],) diff --git a/jnius/jnius_export_func.pxi b/jnius/jnius_export_func.pxi index f02f97f..8633adb 100644 --- a/jnius/jnius_export_func.pxi +++ b/jnius/jnius_export_func.pxi @@ -1,28 +1,31 @@ +from cpython.version cimport PY_MAJOR_VERSION def cast(destclass, obj): cdef JavaClass jc cdef JavaClass jobj = obj - from reflect import autoclass - if isinstance(destclass, basestring): + from .reflect import autoclass + if (PY_MAJOR_VERSION < 3 and isinstance(destclass, basestring)) or \ + (PY_MAJOR_VERSION >=3 and isinstance(destclass, str)): jc = autoclass(destclass)(noinstance=True) else: jc = destclass(noinstance=True) jc.instanciate_from(jobj.j_self) return jc -def find_javaclass(bytes name): +def find_javaclass(namestr): + namestr = namestr.replace('.', '/') + cdef bytes name = str_for_c(namestr) from .reflect import Class cdef JavaClass cls cdef jclass jc cdef JNIEnv *j_env = get_jnienv() - name = name.replace('.', '/') - jc = j_env[0].FindClass(j_env, name) if jc == NULL: raise JavaException('Class not found {0!r}'.format(name)) cls = Class(noinstance=True) cls.instanciate_from(create_local_ref(j_env, jc)) + j_env[0].DeleteLocalRef(j_env, jc) return cls diff --git a/jnius/jnius_jvm_desktop.pxi b/jnius/jnius_jvm_desktop.pxi index 114647a..e3834d9 100644 --- a/jnius/jnius_jvm_desktop.pxi +++ b/jnius/jnius_jvm_desktop.pxi @@ -1,9 +1,12 @@ +from cpython.version cimport PY_MAJOR_VERSION + # on desktop, we need to create an env :) # example taken from http://www.inonit.com/cygwin/jni/invocationApi/c.html -cdef extern jint JNI_CreateJavaVM(JavaVM **pvm, void **penv, void *args) +cdef extern jint __stdcall JNI_CreateJavaVM(JavaVM **pvm, void **penv, void *args) cdef extern from "jni.h": int JNI_VERSION_1_4 + int JNI_OK jboolean JNI_FALSE ctypedef struct JavaVMInitArgs: jint version @@ -16,52 +19,38 @@ cdef extern from "jni.h": cdef JNIEnv *_platform_default_env = NULL -def classpath(): - import platform - from glob import glob - from os import environ - from os.path import realpath, dirname, join - - if platform.system() == 'Windows': - split_char = ';' - else: - split_char = ':' - - paths = [realpath('.'), join(dirname(__file__), 'src'), ] - if 'CLASSPATH' not in environ: - return split_char.join(paths) - - cp = environ.get('CLASSPATH') - pre_paths = paths + cp.split(split_char) - # deal with wildcards - for path in pre_paths: - if not path.endswith('*'): - paths.append(path) - else: - paths.extend(glob(path + '.jar')) - paths.extend(glob(path + '.JAR')) - result = split_char.join(paths) - return result - -cdef void create_jnienv(): +cdef void create_jnienv() except *: cdef JavaVM* jvm cdef JavaVMInitArgs args - cdef JavaVMOption options[1] + cdef JavaVMOption *options + cdef int ret cdef bytes py_bytes + import jnius_config - cp = classpath() - py_bytes = ('-Djava.class.path={0}'.format(cp)) - options[0].optionString = py_bytes - options[0].extraInfo = NULL + optarr = jnius_config.options + optarr.append("-Djava.class.path=" + jnius_config.expand_classpath()) + + options = malloc(sizeof(JavaVMOption) * len(optarr)) + for i, opt in enumerate(optarr): + if PY_MAJOR_VERSION >= 3: + opt = opt.encode('utf-8') + options[i].optionString = (opt) + options[i].extraInfo = NULL args.version = JNI_VERSION_1_4 args.options = options - args.nOptions = 1 + args.nOptions = len(optarr) args.ignoreUnrecognized = JNI_FALSE - JNI_CreateJavaVM(&jvm, &_platform_default_env, &args) + ret = JNI_CreateJavaVM(&jvm, &_platform_default_env, &args) + free(options) -cdef JNIEnv *get_platform_jnienv(): + if ret != JNI_OK: + raise SystemError("JVM failed to start") + + jnius_config.vm_running = True + +cdef JNIEnv *get_platform_jnienv() except NULL: if _platform_default_env == NULL: create_jnienv() return _platform_default_env diff --git a/jnius/jnius_jvm_dlopen.pxi b/jnius/jnius_jvm_dlopen.pxi new file mode 100644 index 0000000..9fce931 --- /dev/null +++ b/jnius/jnius_jvm_dlopen.pxi @@ -0,0 +1,90 @@ +include "config.pxi" +import os + +cdef extern from *: + ctypedef char const_char "const char" + +cdef extern from 'dlfcn.h' nogil: + void* dlopen(const_char *filename, int flag) + char *dlerror() + void *dlsym(void *handle, const_char *symbol) + int dlclose(void *handle) + + unsigned int RTLD_LAZY + unsigned int RTLD_NOW + unsigned int RTLD_GLOBAL + unsigned int RTLD_LOCAL + unsigned int RTLD_NODELETE + unsigned int RTLD_NOLOAD + unsigned int RTLD_DEEPBIND + + unsigned int RTLD_DEFAULT + long unsigned int RTLD_NEXT + + +cdef extern from "jni.h": + int JNI_VERSION_1_6 + int JNI_OK + jboolean JNI_FALSE + ctypedef struct JavaVMInitArgs: + jint version + jint nOptions + jboolean ignoreUnrecognized + JavaVMOption *options + ctypedef struct JavaVMOption: + char *optionString + void *extraInfo + +cdef JNIEnv *_platform_default_env = NULL + +cdef void create_jnienv() except *: + cdef JavaVM* jvm + cdef JavaVMInitArgs args + cdef JavaVMOption *options + cdef int ret + cdef bytes py_bytes + import jnius_config + + JAVA_HOME = os.environ['JAVA_HOME'] + if JAVA_HOME is None or JAVA_HOME == '': + raise SystemError("JAVA_HOME is not set.") + IF JNIUS_PYTHON3: + lib_path = str_for_c(os.path.join(JAVA_HOME, JNIUS_LIB_SUFFIX.decode("utf-8"))) + ELSE: + lib_path = str_for_c(os.path.join(JAVA_HOME, JNIUS_LIB_SUFFIX)) + + cdef void *handle = dlopen(lib_path, RTLD_NOW | RTLD_GLOBAL) + if handle == NULL: + raise SystemError("Error calling dlopen({0}: {1}".format(lib_path, dlerror())) + + cdef void *jniCreateJVM = dlsym(handle, b"JNI_CreateJavaVM") + + if jniCreateJVM == NULL: + raise SystemError("Error calling dlfcn for JNI_CreateJavaVM: {0}".format(dlerror())) + + optarr = jnius_config.options + optarr.append("-Djava.class.path=" + jnius_config.expand_classpath()) + + options = malloc(sizeof(JavaVMOption) * len(optarr)) + for i, opt in enumerate(optarr): + optbytes = str_for_c(opt) + options[i].optionString = (optbytes) + options[i].extraInfo = NULL + + args.version = JNI_VERSION_1_6 + args.options = options + args.nOptions = len(optarr) + args.ignoreUnrecognized = JNI_FALSE + + ret = ( jniCreateJVM)(&jvm, &_platform_default_env, &args) + free(options) + + if ret != JNI_OK: + raise SystemError("JVM failed to start: {0}".format(ret)) + + jnius_config.vm_running = True + +cdef JNIEnv *get_platform_jnienv() except NULL: + if _platform_default_env == NULL: + create_jnienv() + return _platform_default_env diff --git a/jnius/jnius_nativetypes.pxi b/jnius/jnius_nativetypes.pxi index 296cb49..279bbb1 100644 --- a/jnius/jnius_nativetypes.pxi +++ b/jnius/jnius_nativetypes.pxi @@ -12,6 +12,8 @@ cdef python_op(int op, object a, object b): elif op == 5: return a != b + + cdef class ByteArray: cdef LocalRef _jobject cdef long _size @@ -23,6 +25,8 @@ cdef class ByteArray: self._buf = NULL self._arr = None + + def __dealloc__(self): cdef JNIEnv *j_env if self._buf != NULL: @@ -47,8 +51,21 @@ cdef class ByteArray: def __len__(self): return self._size - def __getitem__(self, long index): - return self._arr[index] + def __getitem__(self, index): + cdef long xx + if isinstance(index, slice): + val = [] + (start, stop, step) = index.indices(len(self._arr)) + for x in range(start, stop, step): + xx = x + val.append(self._arr[xx]) + return val + else: + xx = index + return self._arr[xx] + + def __getslice__(self, long i, long j): + return self._arr[i:j] def __richcmp__(self, other, op): cdef ByteArray b_other @@ -60,9 +77,6 @@ cdef class ByteArray: else: return False - def __getslice__(self, long i, long j): - return self._arr[i:j] - def tolist(self): return list(self[:]) diff --git a/jnius/jnius_nativetypes3.pxi b/jnius/jnius_nativetypes3.pxi new file mode 100644 index 0000000..c205538 --- /dev/null +++ b/jnius/jnius_nativetypes3.pxi @@ -0,0 +1,78 @@ + +cdef python_op(int op, object a, object b): + if op == 0: + return a < b + elif op == 1: + return a <= b + elif op == 2: + return a == b + elif op == 3: + return a >= b + elif op == 4: + return a > b + elif op == 5: + return a != b + +cdef class ByteArray: + cdef LocalRef _jobject + cdef long _size + cdef jbyte *_buf + cdef jbyte[:] _arr + + def __cinit__(self): + self._size = 0 + self._buf = NULL + self._arr = None + + def __dealloc__(self): + cdef JNIEnv *j_env + if self._buf != NULL: + j_env = get_jnienv() + j_env[0].ReleaseByteArrayElements(j_env, self._jobject.obj, self._buf, 0) + self._buf = NULL + self._jobject = None + + cdef void set_buffer(self, JNIEnv *env, jobject obj, long size, jbyte *buf): + if self._buf != NULL: + raise Exception('Cannot call set_buffer() twice.') + self._jobject = LocalRef() + self._jobject.create(env, obj) + self._size = size + self._buf = buf + self._arr = self._buf + + def __str__(self): + return ''.format( + self._size, id(self)) + + def __len__(self): + return self._size + + def __getitem__(self, index): + cdef long xx + if isinstance(index, slice): + val = [] + (start, stop, step) = index.indices(len(self._arr)) + for x in range(start, stop, step): + xx = x + val.append(self._arr[xx]) + return val + else: + xx = index + return self._arr[xx] + + def __richcmp__(self, other, op): + cdef ByteArray b_other + if isinstance(other, (list, tuple)): + return python_op(op, self.tolist(), other) + elif isinstance(other, ByteArray): + b_other = other + return python_op(op, self.tostring(), other.tostring()) + else: + return False + + def tolist(self): + return list(self[:]) + + def tostring(self): + return self._buf[:self._size] diff --git a/jnius/jnius_proxy.pxi b/jnius/jnius_proxy.pxi index 9e604cd..f7ee437 100644 --- a/jnius/jnius_proxy.pxi +++ b/jnius/jnius_proxy.pxi @@ -72,6 +72,7 @@ cdef class PythonJavaClass(object): return py_method(*args) + cdef jobject py_invoke0(JNIEnv *j_env, jobject j_this, jobject j_proxy, jobject j_method, jobjectArray args) except * with gil: diff --git a/jnius/jnius_utils.pxi b/jnius/jnius_utils.pxi index 4318a96..b1b76da 100644 --- a/jnius/jnius_utils.pxi +++ b/jnius/jnius_utils.pxi @@ -1,3 +1,20 @@ +from cpython.version cimport PY_MAJOR_VERSION + +cdef str_for_c(s): + if PY_MAJOR_VERSION < 3: + if isinstance(s, unicode): + return s.encode('utf-8') + else: + return s + else: + return s.encode('utf-8') + +cdef items_compat(d): + if PY_MAJOR_VERSION >= 3: + return d.items() + else: + return d.iteritems() + cdef parse_definition(definition): # not a function, just a field if definition[0] != '(': @@ -10,10 +27,10 @@ cdef parse_definition(definition): while len(argdef): c = argdef[0] - # read the array char + # read the array char(s) prefix = '' - if c == '[': - prefix = c + while c == '[': + prefix += c argdef = argdef[1:] c = argdef[0] @@ -27,26 +44,137 @@ cdef parse_definition(definition): if c == 'L': c, argdef = argdef.split(';', 1) args.append(prefix + c + ';') + continue + + raise Exception('Invalid "{}" character in definition "{}"'.format( + c, definition[1:])) return ret, tuple(args) cdef void check_exception(JNIEnv *j_env) except *: + cdef jmethodID toString = NULL + cdef jmethodID getCause = NULL + cdef jmethodID getStackTrace = NULL + cdef jmethodID getMessage = NULL + cdef jstring e_msg + cdef jboolean isCopy cdef jthrowable exc = j_env[0].ExceptionOccurred(j_env) + cdef jclass cls_object = NULL + cdef jclass cls_throwable = NULL if exc: - j_env[0].ExceptionDescribe(j_env) + # ExceptionDescribe always writes to stderr, preventing tidy exception + # handling, so should only be for debugging + # j_env[0].ExceptionDescribe(j_env) j_env[0].ExceptionClear(j_env) - raise JavaException('JVM exception occured') + + cls_object = j_env[0].FindClass(j_env, "java/lang/Object") + cls_throwable = j_env[0].FindClass(j_env, "java/lang/Throwable") + + toString = j_env[0].GetMethodID(j_env, cls_object, "toString", "()Ljava/lang/String;"); + getMessage = j_env[0].GetMethodID(j_env, cls_throwable, "getMessage", "()Ljava/lang/String;"); + getCause = j_env[0].GetMethodID(j_env, cls_throwable, "getCause", "()Ljava/lang/Throwable;"); + getStackTrace = j_env[0].GetMethodID(j_env, cls_throwable, "getStackTrace", "()[Ljava/lang/StackTraceElement;"); + + e_msg = j_env[0].CallObjectMethod(j_env, exc, getMessage); + pymsg = None if e_msg == NULL else convert_jobject_to_python(j_env, 'Ljava/lang/String;', e_msg) + + pystack = [] + _append_exception_trace_messages(j_env, pystack, exc, getCause, getStackTrace, toString) + + pyexcclass = lookup_java_object_name(j_env, exc).replace('/', '.') + + j_env[0].DeleteLocalRef(j_env, cls_object) + j_env[0].DeleteLocalRef(j_env, cls_throwable) + if e_msg != NULL: + j_env[0].DeleteLocalRef(j_env, e_msg) + j_env[0].DeleteLocalRef(j_env, exc) + + raise JavaException('JVM exception occurred: %s' % (pymsg if pymsg is not None else pyexcclass), pyexcclass, pymsg, pystack) + + +cdef void _append_exception_trace_messages( + JNIEnv* j_env, + list pystack, + jthrowable exc, + jmethodID mid_getCause, + jmethodID mid_getStackTrace, + jmethodID mid_toString): + + # Get the array of StackTraceElements. + cdef jobjectArray frames = j_env[0].CallObjectMethod(j_env, exc, mid_getStackTrace) + cdef jsize frames_length = j_env[0].GetArrayLength(j_env, frames) + cdef jstring msg_obj + cdef jobject frame + cdef jthrowable cause + + # Add Throwable.toString() before descending stack trace messages. + if frames != NULL: + msg_obj = j_env[0].CallObjectMethod(j_env, exc, mid_toString) + pystr = None if msg_obj == NULL else convert_jobject_to_python(j_env, 'Ljava/lang/String;', msg_obj) + # If this is not the top-of-the-trace then this is a cause. + if len(pystack) > 0: + pystack.append("Caused by:") + pystack.append(pystr) + if msg_obj != NULL: + j_env[0].DeleteLocalRef(j_env, msg_obj) + + # Append stack trace messages if there are any. + if frames_length > 0: + for i in range(frames_length): + # Get the string returned from the 'toString()' method of the next frame and append it to the error message. + frame = j_env[0].GetObjectArrayElement(j_env, frames, i) + msg_obj = j_env[0].CallObjectMethod(j_env, frame, mid_toString) + pystr = None if msg_obj == NULL else convert_jobject_to_python(j_env, 'Ljava/lang/String;', msg_obj) + pystack.append(pystr) + if msg_obj != NULL: + j_env[0].DeleteLocalRef(j_env, msg_obj) + j_env[0].DeleteLocalRef(j_env, frame) + + # If 'exc' has a cause then append the stack trace messages from the cause. + if frames != NULL: + cause = j_env[0].CallObjectMethod(j_env, exc, mid_getCause) + if cause != NULL: + _append_exception_trace_messages(j_env, pystack, cause, + mid_getCause, mid_getStackTrace, mid_toString) + j_env[0].DeleteLocalRef(j_env, cause) + + j_env[0].DeleteLocalRef(j_env, frames) cdef dict assignable_from = {} -cdef void check_assignable_from(JNIEnv *env, JavaClass jc, bytes signature) except *: - cdef jclass cls +cdef int assignable_from_order = 0 +cdef void check_assignable_from(JNIEnv *env, JavaClass jc, signature) except *: + global assignable_from_order + cdef jclass cls, clsA, clsB + cdef jthrowable exc + + # first call, we need to get over the libart issue, which implemented + # IsAssignableFrom the wrong way. + # Ref: https://github.com/kivy/pyjnius/issues/92 + # Google Bug: https://android.googlesource.com/platform/art/+/1268b74%5E!/ + if assignable_from_order == 0: + clsA = env[0].FindClass(env, "java/lang/String") + clsB = env[0].FindClass(env, "java/lang/Object") + if env[0].IsAssignableFrom(env, clsB, clsA): + # Bug triggered, IsAssignableFrom said we can do things like: + # String a = Object() + assignable_from_order = -1 + else: + assignable_from_order = 1 # if we have a JavaObject, it's always ok. if signature == 'java/lang/Object': return + # FIXME Android/libART specific check + # check_jni.cc crash when calling the IsAssignableFrom with + # org/jnius/NativeInvocationHandler java/lang/reflect/InvocationHandler + # Because we know it's ok, just return here. + if signature == 'java/lang/reflect/InvocationHandler' and \ + jc.__javaclass__ == 'org/jnius/NativeInvocationHandler': + return + # if the signature is a direct match, it's ok too :) if jc.__javaclass__ == signature: return @@ -57,13 +185,22 @@ cdef void check_assignable_from(JNIEnv *env, JavaClass jc, bytes signature) exce # we got an object that doesn't match with the signature # check if we can use it. - cls = env[0].FindClass(env, signature) + s = str_for_c(signature) + cls = env[0].FindClass(env, s) if cls == NULL: raise JavaException('Unable to found the class for {0!r}'.format( signature)) - result = bool(env[0].IsAssignableFrom(env, jc.j_cls, cls)) - env[0].ExceptionClear(env) + if assignable_from_order == 1: + result = bool(env[0].IsAssignableFrom(env, jc.j_cls, cls)) + else: + result = bool(env[0].IsAssignableFrom(env, cls, jc.j_cls)) + + exc = env[0].ExceptionOccurred(env) + if exc: + env[0].ExceptionDescribe(env) + env[0].ExceptionClear(env) + assignable_from[(jc.__javaclass__, signature)] = bool(result) if result is False: @@ -71,12 +208,12 @@ cdef void check_assignable_from(JNIEnv *env, JavaClass jc, bytes signature) exce jc.__javaclass__, signature)) -cdef bytes lookup_java_object_name(JNIEnv *j_env, jobject j_obj): +cdef lookup_java_object_name(JNIEnv *j_env, jobject j_obj): cdef jclass jcls = j_env[0].GetObjectClass(j_env, j_obj) cdef jclass jcls2 = j_env[0].GetObjectClass(j_env, jcls) cdef jmethodID jmeth = j_env[0].GetMethodID(j_env, jcls2, 'getName', '()Ljava/lang/String;') cdef jobject js = j_env[0].CallObjectMethod(j_env, jcls, jmeth) - name = convert_jobject_to_python(j_env, b'Ljava/lang/String;', js) + name = convert_jobject_to_python(j_env, 'Ljava/lang/String;', js) j_env[0].DeleteLocalRef(j_env, js) j_env[0].DeleteLocalRef(j_env, jcls) j_env[0].DeleteLocalRef(j_env, jcls2) @@ -86,7 +223,6 @@ cdef bytes lookup_java_object_name(JNIEnv *j_env, jobject j_obj): cdef int calculate_score(sign_args, args, is_varargs=False) except *: cdef int index cdef int score = 0 - cdef bytes r cdef JavaClass jc if len(args) != len(sign_args) and not is_varargs: @@ -157,7 +293,11 @@ cdef int calculate_score(sign_args, args, is_varargs=False) except *: continue # if it's a string, accept any python string - if r == 'java/lang/String' and isinstance(arg, basestring): + if r == 'java/lang/String' and isinstance(arg, basestring) and PY_MAJOR_VERSION < 3: + score += 10 + continue + + if r == 'java/lang/String' and isinstance(arg, str) and PY_MAJOR_VERSION >= 3: score += 10 continue @@ -172,6 +312,11 @@ cdef int calculate_score(sign_args, args, is_varargs=False) except *: continue return -1 + # accept an autoclass class for java/lang/Class. + if hasattr(arg, '__javaclass__') and r == 'java/lang/Class': + score += 10 + continue + # if we pass a JavaClass, ensure the definition is matching # XXX FIXME what if we use a subclass or something ? if isinstance(arg, JavaClass): @@ -204,7 +349,15 @@ cdef int calculate_score(sign_args, args, is_varargs=False) except *: score += 10 continue - if (r == '[B' or r == '[C') and isinstance(arg, basestring): + if (r == '[B' or r == '[C') and isinstance(arg, basestring) and PY_MAJOR_VERSION < 3: + score += 10 + continue + + if (r == '[B') and isinstance(arg, bytes) and PY_MAJOR_VERSION >= 3: + score += 10 + continue + + if (r == '[C') and isinstance(arg, str) and PY_MAJOR_VERSION >= 3: score += 10 continue diff --git a/jnius/reflect.py b/jnius/reflect.py index b3b1ea7..202ad69 100644 --- a/jnius/reflect.py +++ b/jnius/reflect.py @@ -1,16 +1,20 @@ +from __future__ import absolute_import +from __future__ import unicode_literals +from __future__ import print_function +from __future__ import division __all__ = ('autoclass', 'ensureclass') +from six import with_metaclass -from jnius import ( +from .jnius import ( JavaClass, MetaJavaClass, JavaMethod, JavaStaticMethod, JavaField, JavaStaticField, JavaMultipleMethod, find_javaclass ) -class Class(JavaClass): - __metaclass__ = MetaJavaClass +class Class(with_metaclass(MetaJavaClass, JavaClass)): __javaclass__ = 'java/lang/Class' - desiredAssertionStatus = JavaMethod('()Z;') + desiredAssertionStatus = JavaMethod('()Z') forName = JavaMultipleMethod([ ('(Ljava/lang/String,Z,Ljava/lang/ClassLoader;)Ljava/langClass;', True, False), ('(Ljava/lang/String;)Ljava/lang/Class;', True, False), ]) @@ -32,7 +36,7 @@ class Class(JavaClass): getInterfaces = JavaMethod('()[Ljava/lang/Class;') getMethod = JavaMethod('(Ljava/lang/String,[Ljava/lang/Class;)Ljava/lang/reflect/Method;') getMethods = JavaMethod('()[Ljava/lang/reflect/Method;') - getModifiers = JavaMethod('()[I;') + getModifiers = JavaMethod('()[I') getName = JavaMethod('()Ljava/lang/String;') getPackage = JavaMethod('()Ljava/lang/Package;') getProtectionDomain = JavaMethod('()Ljava/security/ProtectionDomain;') @@ -40,25 +44,23 @@ class Class(JavaClass): getResourceAsStream = JavaMethod('(Ljava/lang/String;)Ljava/io/InputStream;') getSigners = JavaMethod('()[Ljava/lang/Object;') getSuperclass = JavaMethod('()Ljava/lang/reflect/Class;') - isArray = JavaMethod('()Z;') - isAssignableFrom = JavaMethod('(Ljava/lang/reflect/Class;)Z;') - isInstance = JavaMethod('(Ljava/lang/Object;)Z;') - isInterface = JavaMethod('()Z;') - isPrimitive = JavaMethod('()Z;') + isArray = JavaMethod('()Z') + isAssignableFrom = JavaMethod('(Ljava/lang/reflect/Class;)Z') + isInstance = JavaMethod('(Ljava/lang/Object;)Z') + isInterface = JavaMethod('()Z') + isPrimitive = JavaMethod('()Z') newInstance = JavaMethod('()Ljava/lang/Object;') toString = JavaMethod('()Ljava/lang/String;') -class Object(JavaClass): - __metaclass__ = MetaJavaClass +class Object(with_metaclass(MetaJavaClass, JavaClass)): __javaclass__ = 'java/lang/Object' getClass = JavaMethod('()Ljava/lang/Class;') hashCode = JavaMethod('()I') -class Modifier(JavaClass): - __metaclass__ = MetaJavaClass +class Modifier(with_metaclass(MetaJavaClass, JavaClass)): __javaclass__ = 'java/lang/reflect/Modifier' isAbstract = JavaStaticMethod('(I)Z') @@ -75,8 +77,7 @@ class Modifier(JavaClass): isVolatile = JavaStaticMethod('(I)Z') -class Method(JavaClass): - __metaclass__ = MetaJavaClass +class Method(with_metaclass(MetaJavaClass, JavaClass)): __javaclass__ = 'java/lang/reflect/Method' getName = JavaMethod('()Ljava/lang/String;') @@ -87,8 +88,7 @@ class Method(JavaClass): isVarArgs = JavaMethod('()Z') -class Field(JavaClass): - __metaclass__ = MetaJavaClass +class Field(with_metaclass(MetaJavaClass, JavaClass)): __javaclass__ = 'java/lang/reflect/Field' getName = JavaMethod('()Ljava/lang/String;') @@ -97,8 +97,7 @@ class Field(JavaClass): getModifiers = JavaMethod('()I') -class Constructor(JavaClass): - __metaclass__ = MetaJavaClass +class Constructor(with_metaclass(MetaJavaClass, JavaClass)): __javaclass__ = 'java/lang/reflect/Constructor' toString = JavaMethod('()Ljava/lang/String;') @@ -137,6 +136,11 @@ def ensureclass(clsname): registers.append(clsname) autoclass(clsname) +def lower_name(s): + return s[:1].lower() + s[1:] if s else '' + +def bean_getter(s): + return (s.startswith('get') and len(s) > 3 and s[3].isupper()) or (s.startswith('is') and len(s) > 2 and s[2].isupper()) def autoclass(clsname): jniname = clsname.replace('.', '/') @@ -176,9 +180,12 @@ def autoclass(clsname): get_signature(method.getReturnType())) cls = JavaStaticMethod if static else JavaMethod classDict[name] = cls(sig, varargs=varargs) + if name != 'getClass' and bean_getter(name) and len(method.getParameterTypes()) == 0: + lowername = lower_name(name[3:]) + classDict[lowername] = (lambda n: property(lambda self: getattr(self, n)()))(name) continue - # multpile signatures + # multiple signatures signatures = [] for index, subname in enumerate(methods_name): if subname != name: @@ -207,6 +214,12 @@ def autoclass(clsname): classDict[name] = JavaMultipleMethod(signatures) + for iclass in c.getInterfaces(): + if iclass.getName() == 'java.util.List': + classDict['__getitem__'] = lambda self, index: self.get(index) + classDict['__len__'] = lambda self: self.size() + break + for field in c.getFields(): static = Modifier.isStatic(field.getModifiers()) sig = get_signature(field.getType()) diff --git a/jnius/signatures.py b/jnius/signatures.py new file mode 100644 index 0000000..9bc1830 --- /dev/null +++ b/jnius/signatures.py @@ -0,0 +1,99 @@ +''' +signatures.py +============= + +A handy API for writing JNI signatures easily + +Author: chrisjrn + +This module aims to provide a more human-friendly API for +wiring up Java proxy methods in PyJnius. + +You can use the signature function to produce JNI method +signautures for methods; passing PyJnius JavaClass classes +as return or argument types; provided here are annotations +representing Java's primitive and array times. + +Methods can return just a standard primitive type: + +>>> signature(jint, ()) +'()I' + +>>> s.signature(jvoid, [jint]) +'(I)V' + +Or you can use autoclass proxies to specify Java classes +for return types. + +>>> from jnius import autoclass +>>> String = autoclass("java.lang.String") +>>> signature(String, ()) +'()Ljava/lang/String;' + +''' + +__version__ = '0.0.1' + +from . import JavaClass +from . import java_method + + +''' Type specifiers for primitives ''' + +class _JavaSignaturePrimitive(object): + _spec = "" + +def _MakeSignaturePrimitive(name, spec): + class __Primitive(_JavaSignaturePrimitive): + ''' PyJnius signature for Java %s type ''' % name + _name = name + _spec = spec + __Primitive.__name__ = "j" + name + + return __Primitive + +jboolean = _MakeSignaturePrimitive("boolean", "Z") +jbyte = _MakeSignaturePrimitive("byte", "B") +jchar = _MakeSignaturePrimitive("char", "C") +jdouble = _MakeSignaturePrimitive("double", "D") +jfloat = _MakeSignaturePrimitive("float", "F") +jint = _MakeSignaturePrimitive("int", "I") +jlong = _MakeSignaturePrimitive("long", "J") +jshort = _MakeSignaturePrimitive("short", "S") +jvoid = _MakeSignaturePrimitive("void", "V") + +def JArray(of_type): + ''' Signature helper for identifying arrays of a given object or + primitive type. ''' + + spec = "[" + _jni_type_spec(of_type) + return _MakeSignaturePrimitive("array", spec) + +def with_signature(returns, takes): + ''' Alternative version of @java_method that takes JavaClass + objects to produce the method signature. ''' + + sig = signature(returns, takes) + return java_method(sig) + +def signature(returns, takes): + ''' Produces a JNI method signature, taking the provided arguments + and returning the given return type. ''' + + out_takes = [] + for arg in takes: + out_takes.append(_jni_type_spec(arg)) + + return "(" + "".join(out_takes) + ")" + _jni_type_spec(returns) + +def _jni_type_spec(jclass): + ''' Produces a JNI type specification string for the given argument. + If the argument is a jnius.JavaClass, it produces the JNI type spec + for the class. Signature primitives return their stored type spec. + ''' + + if issubclass(jclass, JavaClass): + return "L" + jclass.__javaclass__ + ";" + elif issubclass(jclass, _JavaSignaturePrimitive): + return jclass._spec + \ No newline at end of file diff --git a/jnius_config.py b/jnius_config.py new file mode 100644 index 0000000..bb5ba95 --- /dev/null +++ b/jnius_config.py @@ -0,0 +1,79 @@ +__all__ = ('set_options', 'add_options', 'get_options', + 'set_classpath', 'add_classpath', 'get_classpath', + 'expand_classpath') + +import platform +if platform.system() == 'Windows': + split_char = ';' +else: + split_char = ':' + +vm_running = False +options = [] +classpath = None + +def set_options(*opts): + "Sets the list of options to the JVM. Removes any previously set options." + if vm_running: + raise ValueError("VM is already running, can't set options") + globals()['options'] = opts + +def add_options(*opts): + "Appends options to the list of VM options." + if vm_running: + raise ValueError("VM is already running, can't set options") + global options + options.extend(opts) + +def get_options(): + "Retrieves the current list of VM options." + global options + return list(options) + + +def set_classpath(*path): + """ + Sets the classpath for the JVM to use. Replaces any existing classpath, overriding the CLASSPATH environment variable. + """ + if vm_running: + raise ValueError("VM is already running, can't set classpath") + global classpath + classpath = path + +def add_classpath(*path): + """ + Appends items to the classpath for the JVM to use. + Replaces any existing classpath, overriding the CLASSPATH environment variable. + """ + if vm_running: + raise ValueError("VM is already running, can't set classpath") + global classpath + if classpath is None: + classpath = list(path) + else: + classpath.extend(path) + +def get_classpath(): + "Retrieves the classpath the JVM will use." + from os import environ + from os.path import realpath + global classpath + + if classpath is not None: + return list(classpath) + + if 'CLASSPATH' in environ: + return environ['CLASSPATH'].split(split_char) + + return [realpath('.')] + +def expand_classpath(): + from glob import glob + paths = [] + # deal with wildcards + for path in get_classpath(): + if not path.endswith('*'): + paths.append(path) + else: + paths.extend(glob(path + '.[Jj][Aa][Rr]')) + return split_char.join(paths) diff --git a/setup.py b/setup.py index 1715e82..3f277c3 100644 --- a/setup.py +++ b/setup.py @@ -1,7 +1,24 @@ -from distutils.core import setup, Extension +from __future__ import print_function +try: + from setuptools import setup, Extension +except ImportError: + from distutils.core import setup, Extension from os import environ from os.path import dirname, join, exists import sys +from platform import architecture + +PY3 = sys.version_info >= (3,0,0) + +def getenv(key): + val = environ.get(key) + if val is not None: + if PY3: + return val.decode() + else: + return val + else: + return val files = [ 'jni.pxi', @@ -10,6 +27,7 @@ files = [ 'jnius_export_func.pxi', 'jnius_jvm_android.pxi', 'jnius_jvm_desktop.pxi', + 'jnius_jvm_dlopen.pxi', 'jnius_localref.pxi', 'jnius.pyx', 'jnius_utils.pxi', @@ -17,14 +35,15 @@ files = [ libraries = [] library_dirs = [] +lib_location = None extra_link_args = [] include_dirs = [] -install_requires = [] +install_requires = ['six'] # detect Python for android platform = sys.platform -ndkplatform = environ.get('NDKPLATFORM') -if ndkplatform is not None and environ.get('LIBLINK'): +ndkplatform = getenv('NDKPLATFORM') +if ndkplatform is not None and getenv('LIBLINK'): platform = 'android' # detect cython @@ -32,34 +51,58 @@ try: from Cython.Distutils import build_ext install_requires.append('cython') except ImportError: - from distutils.command.build_ext import build_ext + try: + from setuptools.command.build_ext import build_ext + except ImportError: + from distutils.command.build_ext import build_ext if platform != 'android': - print '\n\nYou need Cython to compile Pyjnius.\n\n' + print('\n\nYou need Cython to compile Pyjnius.\n\n') raise + # On Android we expect to see 'c' files lying about. + # and we go ahead with the 'desktop' file? Odd. files = [fn[:-3] + 'c' for fn in files if fn.endswith('pyx')] if platform == 'android': # for android, we use SDL... libraries = ['sdl', 'log'] - library_dirs = ['libs/' + environ['ARCH']] + library_dirs = ['libs/' + getenv('ARCH')] elif platform == 'darwin': - import objc - framework = objc.pathForFramework('JavaVM.framework') + import subprocess + framework = subprocess.Popen('/usr/libexec/java_home', + shell=True, stdout=subprocess.PIPE).communicate()[0] + if PY3: + framework = framework.decode(); + framework = framework.strip() + print('java_home: {0}\n'.format(framework)); if not framework: raise Exception('You must install Java on your Mac OS X distro') - extra_link_args = ['-framework', 'JavaVM'] - include_dirs = [join(framework, 'Versions/A/Headers')] + if '1.6' in framework: + lib_location = '../Libraries/libjvm.dylib' + include_dirs = [join(framework, 'System/Library/Frameworks/JavaVM.framework/Versions/Current/Headers')] + else: + lib_location = 'jre/lib/server/libjvm.dylib' + include_dirs = ['{0}/include'.format(framework), '{0}/include/darwin'.format(framework)] else: import subprocess # otherwise, we need to search the JDK_HOME - jdk_home = environ.get('JDK_HOME') - if not jdk_home: - jdk_home = subprocess.Popen('readlink -f `which javac` | sed "s:bin/javac::"', - shell=True, stdout=subprocess.PIPE).communicate()[0].strip() + jdk_home = getenv('JDK_HOME') if not jdk_home: + if platform == 'win32': + env_var = getenv('JAVA_HOME') + if env_var and 'jdk' in env_var: + jdk_home = env_var + + # Remove /bin if it's appended to JAVA_HOME + if jdk_home[-3:] == 'bin': + jdk_home = jdk_home[:-4] + else: + jdk_home = subprocess.Popen('readlink -f `which javac` | sed "s:bin/javac::"', + shell=True, stdout=subprocess.PIPE).communicate()[0].strip() + if jdk_home is not None and PY3: + jdk_home = jdk_home.decode() + if not jdk_home or not exists(jdk_home): raise Exception('Unable to determine JDK_HOME') - jre_home = environ.get('JRE_HOME') if exists(join(jdk_home, 'jre')): jre_home = join(jdk_home, 'jre') if not jre_home: @@ -67,17 +110,33 @@ else: shell=True, stdout=subprocess.PIPE).communicate()[0].strip() if not jre_home: raise Exception('Unable to determine JRE_HOME') - cpu = 'i386' if sys.maxint == 2147483647 else 'amd64' + cpu = 'amd64' if architecture()[0] == '64bit' else 'i386' + + if platform == 'win32': + incl_dir = join(jdk_home, 'include', 'win32') + libraries = ['jvm'] + else: + incl_dir = join(jdk_home, 'include', 'linux') + lib_location = 'jre/lib/amd64/server/libjvm.so' + include_dirs = [ join(jdk_home, 'include'), - join(jdk_home, 'include', 'linux')] - library_dirs = [join(jre_home, 'lib', cpu, 'server')] - extra_link_args = ['-Wl,-rpath', library_dirs[0]] - libraries = ['jvm'] + incl_dir] + + if platform == 'win32': + library_dirs = [ + join(jdk_home, 'lib'), + join(jre_home, 'bin', 'server')] # generate the config.pxi with open(join(dirname(__file__), 'jnius', 'config.pxi'), 'w') as fd: - fd.write('DEF JNIUS_PLATFORM = {0!r}'.format(platform)) + fd.write('DEF JNIUS_PLATFORM = {0!r}\n\n'.format(platform)) + if PY3: + fd.write('DEF JNIUS_PYTHON3 = True\n\n') + else: + fd.write('DEF JNIUS_PYTHON3 = False\n\n') + if lib_location is not None: + fd.write('DEF JNIUS_LIB_SUFFIX = {0!r}\n\n'.format(lib_location)) with open(join('jnius', '__init__.py')) as fd: versionline = [x for x in fd.readlines() if x.startswith('__version__')] @@ -88,10 +147,11 @@ setup(name='jnius', version=version, cmdclass={'build_ext': build_ext}, packages=['jnius'], + py_modules=['jnius_config'], url='http://pyjnius.readthedocs.org/', author='Mathieu Virbel and Gabriel Pettier', author_email='mat@kivy.org,gabriel@kivy.org', - license='LGPL', + license='MIT', description='Python library to access Java classes', install_requires=install_requires, ext_package='jnius', @@ -106,12 +166,14 @@ setup(name='jnius', classifiers=[ 'Development Status :: 4 - Beta', 'Intended Audience :: Developers', - 'License :: OSI Approved :: GNU Library or Lesser ' - 'General Public License (LGPL)', + 'License :: OSI Approved :: MIT License', 'Natural Language :: English', - 'Operating System :: MacOS :: MacOS X', + 'Operating System :: MacOS :: OS X', 'Operating System :: Microsoft :: Windows', 'Operating System :: POSIX :: Linux', 'Programming Language :: Python :: 2.6', 'Programming Language :: Python :: 2.7', + 'Programming Language :: Python :: 3.3', + 'Programming Language :: Python :: 3.4', + 'Programming Language :: Python :: 3.5', 'Topic :: Software Development :: Libraries :: Application Frameworks']) diff --git a/tests/org/jnius/BasicsTest.java b/tests/java-src/org/jnius/BasicsTest.java similarity index 71% rename from tests/org/jnius/BasicsTest.java rename to tests/java-src/org/jnius/BasicsTest.java index c257822..9fa4abf 100644 --- a/tests/org/jnius/BasicsTest.java +++ b/tests/java-src/org/jnius/BasicsTest.java @@ -22,6 +22,17 @@ public class BasicsTest { public float methodF() { return 1.23456789f; }; public double methodD() { return 1.23456789; }; public String methodString() { return new String("helloworld"); } + public void methodException(int depth) throws IllegalArgumentException { + if (depth == 0) throw new IllegalArgumentException("helloworld"); + else methodException(depth -1); + } + public void methodExceptionChained() throws IllegalArgumentException { + try { + methodException(5); + } catch (IllegalArgumentException e) { + throw new IllegalArgumentException("helloworld2", e); + } + } static public boolean fieldStaticZ = true; static public byte fieldStaticB = 127; @@ -43,6 +54,19 @@ public class BasicsTest { public double fieldD = 1.23456789; public String fieldString = new String("helloworld"); + public boolean fieldSetZ; + public byte fieldSetB; + public char fieldSetC; + public short fieldSetS; + public int fieldSetI; + public long fieldSetJ; + public float fieldSetF; + public double fieldSetD; + public String fieldSetString; + + // Floating-point comparison epsilon + private final static double EPSILON = 1E-6; + public BasicsTest() {} public BasicsTest(byte fieldBVal) { fieldB = fieldBVal; @@ -97,9 +121,10 @@ public class BasicsTest { public boolean methodParamsZBCSIJFD(boolean x1, byte x2, char x3, short x4, int x5, long x6, float x7, double x8) { - // ADD float / double, but dunno how to do with approx return (x1 == true && x2 == 127 && x3 == 'k' && x4 == 32767 && - x5 == 2147483467 && x6 == 2147483467); + x5 == 2147483467 && x6 == 2147483467 && + (Math.abs(x7 - 1.23456789f) < EPSILON) && + (Math.abs(x8 - 1.23456789) < EPSILON)); } public boolean methodParamsString(String s) { @@ -141,4 +166,44 @@ public class BasicsTest { return false; return (x[0] == 127 && x[1] == 127 && x[2] == 127); } + + public void fillByteArray(byte[] x) { + if (x.length != 3) + return; + x[0] = 127; + x[1] = 1; + x[2] = -127; + } + + public boolean testFieldSetZ() { + return (fieldSetZ == true); + } + + public boolean testFieldSetB() { + return (fieldSetB == 127); + } + + public boolean testFieldSetC() { + return (fieldSetC == 'k'); + } + + public boolean testFieldSetS() { + return (fieldSetS == 32767); + } + + public boolean testFieldSetI() { + return (fieldSetI == 2147483467); + } + + public boolean testFieldSetJ() { + return (fieldSetJ == 2147483467); + } + + public boolean testFieldSetF() { + return (Math.abs(fieldSetF - 1.23456789f) < EPSILON); + } + + public boolean testFieldSetD() { + return (Math.abs(fieldSetD - 1.23456789) < EPSILON); + } } diff --git a/tests/org/jnius/ClassArgument.java b/tests/java-src/org/jnius/ClassArgument.java similarity index 100% rename from tests/org/jnius/ClassArgument.java rename to tests/java-src/org/jnius/ClassArgument.java diff --git a/tests/org/jnius/HelloWorld.java b/tests/java-src/org/jnius/HelloWorld.java similarity index 100% rename from tests/org/jnius/HelloWorld.java rename to tests/java-src/org/jnius/HelloWorld.java diff --git a/tests/org/jnius/InterfaceWithPublicEnum.java b/tests/java-src/org/jnius/InterfaceWithPublicEnum.java similarity index 100% rename from tests/org/jnius/InterfaceWithPublicEnum.java rename to tests/java-src/org/jnius/InterfaceWithPublicEnum.java diff --git a/tests/java-src/org/jnius/MultipleDimensions.java b/tests/java-src/org/jnius/MultipleDimensions.java new file mode 100644 index 0000000..9deeec5 --- /dev/null +++ b/tests/java-src/org/jnius/MultipleDimensions.java @@ -0,0 +1,16 @@ +package org.jnius; + +public class MultipleDimensions { + public static boolean methodParamsMatrixI(int[][] x) { + if (x.length != 3 || x[0].length != 3) + return false; + return (x[0][0] == 1 && x[0][1] == 2 && x[1][2] == 6); + } + public static int[][] methodReturnMatrixI() { + int[][] matrix = {{1,2,3}, + {4,5,6}, + {7,8,9}}; + return matrix; + } + +} diff --git a/tests/org/jnius/MultipleMethods.java b/tests/java-src/org/jnius/MultipleMethods.java similarity index 100% rename from tests/org/jnius/MultipleMethods.java rename to tests/java-src/org/jnius/MultipleMethods.java diff --git a/tests/org/jnius/SimpleEnum.java b/tests/java-src/org/jnius/SimpleEnum.java similarity index 100% rename from tests/org/jnius/SimpleEnum.java rename to tests/java-src/org/jnius/SimpleEnum.java diff --git a/tests/test_assignable.py b/tests/test_assignable.py index 9306250..9a2783a 100644 --- a/tests/test_assignable.py +++ b/tests/test_assignable.py @@ -1,3 +1,6 @@ +from __future__ import print_function +from __future__ import division +from __future__ import absolute_import import unittest from jnius import autoclass, JavaException diff --git a/tests/test_bad_declaration.py b/tests/test_bad_declaration.py index a7b46a7..93b6b33 100644 --- a/tests/test_bad_declaration.py +++ b/tests/test_bad_declaration.py @@ -1,3 +1,6 @@ +from __future__ import print_function +from __future__ import division +from __future__ import absolute_import import unittest from jnius import JavaException, JavaClass from jnius.reflect import autoclass @@ -21,3 +24,31 @@ class BadDeclarationTest(unittest.TestCase): Stack = autoclass('java.util.Stack') stack = Stack() self.assertRaises(JavaException, stack.push, 'hello', 'world', 123) + + def test_java_exception_handling(self): + Stack = autoclass('java.util.Stack') + stack = Stack() + try: + stack.pop() + self.fail("Expected exception to be thrown") + except JavaException as je: + # print "Got JavaException: " + str(je) + # print "Got Exception Class: " + je.classname + # print "Got stacktrace: \n" + '\n'.join(je.stacktrace) + self.assertEquals("java.util.EmptyStackException", je.classname) + + def test_java_exception_chaining(self): + BasicsTest = autoclass('org.jnius.BasicsTest') + basics = BasicsTest() + try: + basics.methodExceptionChained() + self.fail("Expected exception to be thrown") + except JavaException as je: + # print "Got JavaException: " + str(je) + # print "Got Exception Class: " + je.classname + # print "Got Exception Message: " + je.innermessage + # print "Got stacktrace: \n" + '\n'.join(je.stacktrace) + self.assertEquals("java.lang.IllegalArgumentException", je.classname) + self.assertEquals("helloworld2", je.innermessage) + self.assertIn("Caused by:", je.stacktrace) + self.assertEquals(11, len(je.stacktrace)) diff --git a/tests/test_basics.py b/tests/test_basics.py index 3964561..d6ebd5c 100644 --- a/tests/test_basics.py +++ b/tests/test_basics.py @@ -1,3 +1,6 @@ +from __future__ import print_function +from __future__ import division +from __future__ import absolute_import import unittest from jnius.reflect import autoclass @@ -55,6 +58,26 @@ class BasicsTest(unittest.TestCase): self.assertEquals(test.fieldB, 127) self.assertEquals(test2.fieldB, 10) + def test_instance_set_fields(self): + test = autoclass('org.jnius.BasicsTest')() + test.fieldSetZ = True + test.fieldSetB = 127 + test.fieldSetC = ord('k') + test.fieldSetS = 32767 + test.fieldSetI = 2147483467 + test.fieldSetJ = 2147483467 + test.fieldSetF = 1.23456789 + test.fieldSetD = 1.23456789 + + self.assertTrue(test.testFieldSetZ()) + self.assertTrue(test.testFieldSetB()) + self.assertTrue(test.testFieldSetC()) + self.assertTrue(test.testFieldSetS()) + self.assertTrue(test.testFieldSetI()) + self.assertTrue(test.testFieldSetJ()) + self.assertTrue(test.testFieldSetF()) + self.assertTrue(test.testFieldSetD()) + def test_instances_methods_array(self): test = autoclass('org.jnius.BasicsTest')() self.assertEquals(test.methodArrayZ(), [True] * 3) @@ -98,7 +121,7 @@ class BasicsTest(unittest.TestCase): def test_instances_methods_params_object_list_long(self): test = autoclass('org.jnius.BasicsTest')() - self.assertEquals(test.methodParamsObject([1L, 2L]), True) + self.assertEquals(test.methodParamsObject([1, 2]), True) def test_instances_methods_params_array_byte(self): test = autoclass('org.jnius.BasicsTest')() diff --git a/tests/test_bytearray.py b/tests/test_bytearray.py index 46a7296..50fcd0b 100644 --- a/tests/test_bytearray.py +++ b/tests/test_bytearray.py @@ -1,3 +1,6 @@ +from __future__ import print_function +from __future__ import division +from __future__ import absolute_import import unittest from jnius import autoclass @@ -6,4 +9,16 @@ class StringArgumentForByteArrayTest(unittest.TestCase): def test_string_arg_for_byte_array(self): # the ByteBuffer.wrap() accept only byte[]. ByteBuffer = autoclass('java.nio.ByteBuffer') - self.assertIsNotNone(ByteBuffer.wrap('hello world')) + self.assertIsNotNone(ByteBuffer.wrap(b'hello world')) + + def test_string_arg_with_signed_char(self): + ByteBuffer = autoclass('java.nio.ByteBuffer') + self.assertIsNotNone(ByteBuffer.wrap(b'\x00\xffHello World\x7f')) + + def test_fill_byte_array(self): + arr = [0, 0, 0] + Test = autoclass('org.jnius.BasicsTest')() + Test.fillByteArray(arr) + self.assertEquals( + arr, + [127, 1, -127]) diff --git a/tests/test_cast.py b/tests/test_cast.py index 6498821..6562cd9 100644 --- a/tests/test_cast.py +++ b/tests/test_cast.py @@ -1,3 +1,6 @@ +from __future__ import print_function +from __future__ import division +from __future__ import absolute_import import unittest from jnius.reflect import autoclass from jnius import cast diff --git a/tests/test_class_argument.py b/tests/test_class_argument.py index f4707fd..31f4b40 100644 --- a/tests/test_class_argument.py +++ b/tests/test_class_argument.py @@ -1,3 +1,6 @@ +from __future__ import print_function +from __future__ import division +from __future__ import absolute_import import unittest from jnius.reflect import autoclass diff --git a/tests/test_enum.py b/tests/test_enum.py index 8c10392..d590639 100644 --- a/tests/test_enum.py +++ b/tests/test_enum.py @@ -1,3 +1,6 @@ +from __future__ import print_function +from __future__ import division +from __future__ import absolute_import import unittest from jnius.reflect import autoclass diff --git a/tests/test_implementation.py b/tests/test_implementation.py index 9237ac4..81b66be 100644 --- a/tests/test_implementation.py +++ b/tests/test_implementation.py @@ -1,4 +1,7 @@ # -*- coding: utf-8 -*- +from __future__ import print_function +from __future__ import division +from __future__ import absolute_import import unittest from jnius.reflect import autoclass diff --git a/tests/test_interface.py b/tests/test_interface.py index 61b9a5c..1d8282a 100644 --- a/tests/test_interface.py +++ b/tests/test_interface.py @@ -1,3 +1,6 @@ +from __future__ import print_function +from __future__ import division +from __future__ import absolute_import import unittest from jnius import autoclass, JavaException diff --git a/tests/test_jnitable_overflow.py b/tests/test_jnitable_overflow.py index 4bf379d..0a98bc7 100644 --- a/tests/test_jnitable_overflow.py +++ b/tests/test_jnitable_overflow.py @@ -1,3 +1,6 @@ +from __future__ import print_function +from __future__ import division +from __future__ import absolute_import # run it, and check with Java VisualVM if we are eating too much memory or not! if __name__ == '__main__': from jnius import autoclass diff --git a/tests/test_method_multiple_signatures.py b/tests/test_method_multiple_signatures.py index b13db57..3e4266f 100644 --- a/tests/test_method_multiple_signatures.py +++ b/tests/test_method_multiple_signatures.py @@ -1,3 +1,6 @@ +from __future__ import print_function +from __future__ import division +from __future__ import absolute_import import unittest from jnius.reflect import autoclass diff --git a/tests/test_multidimension.py b/tests/test_multidimension.py new file mode 100644 index 0000000..0c4b7de --- /dev/null +++ b/tests/test_multidimension.py @@ -0,0 +1,15 @@ +from __future__ import print_function +from __future__ import division +from __future__ import absolute_import +import unittest +from jnius.reflect import autoclass + +class MultipleDimensionsTest(unittest.TestCase): + + def test_multiple_dimensions(self): + MultipleDims = autoclass('org.jnius.MultipleDimensions') + matrix = [[1, 2, 3], + [4, 5, 6], + [7, 8, 9]] + self.assertEquals(MultipleDims.methodParamsMatrixI(matrix), True) + self.assertEquals(MultipleDims.methodReturnMatrixI(), matrix) diff --git a/tests/test_output_args.py b/tests/test_output_args.py index e8f558e..2547357 100644 --- a/tests/test_output_args.py +++ b/tests/test_output_args.py @@ -1,3 +1,6 @@ +from __future__ import print_function +from __future__ import division +from __future__ import absolute_import import unittest from jnius import autoclass diff --git a/tests/test_proxy.py b/tests/test_proxy.py index f89f80f..1ed01b9 100644 --- a/tests/test_proxy.py +++ b/tests/test_proxy.py @@ -1,6 +1,12 @@ -from jnius import autoclass, java_method, PythonJavaClass, cast +from __future__ import print_function +from __future__ import division +from __future__ import absolute_import +from six.moves import range -print '1: declare a TestImplem that implement Collection' +from jnius import autoclass, java_method, PythonJavaClass, cast +from nose.tools import * + +print('1: declare a TestImplem that implement Collection') class TestImplemIterator(PythonJavaClass): @@ -96,47 +102,65 @@ class TestImplem(PythonJavaClass): return it -print '2: instanciate the class, with some data' -a = TestImplem(*range(10)) -print a -print dir(a) +class TestBadSignature(PythonJavaClass): + __javainterfaces__ = ['java/util/List'] -print 'tries to get a ListIterator' + @java_method('(Landroid/bluetooth/BluetoothDevice;IB[])V') + def bad_signature(self, *args): + pass + + +print('2: instantiate the class, with some data') +a = TestImplem(*list(range(10))) +print(a) +print(dir(a)) + +print('tries to get a ListIterator') iterator = a.listIterator() -print 'iterator is', iterator +print('iterator is', iterator) while iterator.hasNext(): - print 'at index', iterator.index, 'value is', iterator.next() + print('at index', iterator.index, 'value is', iterator.next()) -print '3: Do cast to a collection' +print('3: Do cast to a collection') a2 = cast('java/util/Collection', a.j_self) -print a2 +print(a2) -print '4: Try few method on the collection' +print('4: Try few method on the collection') Collections = autoclass('java.util.Collections') #print Collections.enumeration(a) #print Collections.enumeration(a) ret = Collections.max(a) -print "reverse" -print Collections.reverse(a) -print a.data +print("reverse") +print(Collections.reverse(a)) +print(a.data) -print "before swap" -print Collections.swap(a, 2, 3) -print "after swap" -print a.data +print("before swap") +print(Collections.swap(a, 2, 3)) +print("after swap") +print(a.data) -print "rotate" -print Collections.rotate(a, 5) -print a.data +print("rotate") +print(Collections.rotate(a, 5)) +print(a.data) -print 'Order of data before shuffle()', a.data -print Collections.shuffle(a) -print 'Order of data after shuffle()', a.data +print('Order of data before shuffle()', a.data) +print(Collections.shuffle(a)) +print('Order of data after shuffle()', a.data) # XXX We have issues for methosd with multiple signature -print '-> Collections.max(a)' -print Collections.max(a2) +print('-> Collections.max(a)') +print(Collections.max(a2)) #print '-> Collections.shuffle(a)' #print Collections.shuffle(a2) + +# test bad signature +threw = False +try: + TestBadSignature() +except Exception: + threw = True + +if not threw: + raise Exception("Failed to throw for bad signature") diff --git a/tests/test_reflect.py b/tests/test_reflect.py index fedc95a..fc6e8c9 100644 --- a/tests/test_reflect.py +++ b/tests/test_reflect.py @@ -1,3 +1,6 @@ +from __future__ import print_function +from __future__ import division +from __future__ import absolute_import import unittest from jnius.reflect import autoclass diff --git a/tests/test_signature.py b/tests/test_signature.py new file mode 100644 index 0000000..07c9422 --- /dev/null +++ b/tests/test_signature.py @@ -0,0 +1,174 @@ +import unittest + +from jnius import autoclass, java_method, PythonJavaClass, cast + +from jnius.signatures import * + +JObject = autoclass('java/lang/Object') +JString = autoclass('java/lang/String') +JListIterator = autoclass("java.util.ListIterator") + +class TestImplemIterator(PythonJavaClass): + __javainterfaces__ = [ + 'java/util/ListIterator', ] + + def __init__(self, collection, index=0): + super(TestImplemIterator, self).__init__() + self.collection = collection + self.index = index + + @with_signature(jboolean, []) + def hasNext(self): + return self.index < len(self.collection.data) - 1 + + @with_signature(JObject, []) + def next(self): + obj = self.collection.data[self.index] + self.index += 1 + return obj + + @with_signature(jboolean, []) + def hasPrevious(self): + return self.index >= 0 + + @with_signature(JObject, []) + def previous(self): + self.index -= 1 + obj = self.collection.data[self.index] + return obj + + @with_signature(jint, []) + def previousIndex(self): + return self.index - 1 + + @with_signature(JString, []) + def toString(self): + return repr(self) + + @with_signature(JObject, [jint]) + def get(self, index): + return self.collection.data[index - 1] + + @with_signature(jvoid, [JObject]) + def set(self, obj): + self.collection.data[self.index - 1] = obj + + +class TestImplem(PythonJavaClass): + __javainterfaces__ = ['java/util/List'] + + def __init__(self, *args): + super(TestImplem, self).__init__(*args) + self.data = list(args) + + @with_signature(autoclass("java.util.Iterator"), []) + def iterator(self): + it = TestImplemIterator(self) + return it + + @with_signature(JString, []) + def toString(self): + return repr(self) + + @with_signature(jint, []) + def size(self): + return len(self.data) + + @with_signature(JObject, [jint]) + def get(self, index): + return self.data[index] + + @with_signature(JObject, [jint, JObject]) + def set(self, index, obj): + old_object = self.data[index] + self.data[index] = obj + return old_object + + @with_signature(JArray(JObject), []) + def toArray(self): + return self.data + + @with_signature(JListIterator, []) + def listIterator(self): + it = TestImplemIterator(self) + return it + + # TODO cover this case of listIterator. + @java_method(signature(JListIterator, [jint]), + name='ListIterator') + def listIteratorI(self, index): + it = TestImplemIterator(self, index) + return it + + +from jnius.reflect import autoclass + +class SignaturesTest(unittest.TestCase): + + def test_construct_stack_from_testimplem(self): + Stack = autoclass("java.util.Stack") + pyjlist = TestImplem(1, 2, 3, 4, 5, 6, 7) + stack = Stack() + stack.addAll(pyjlist) + self.assertEquals(7, pyjlist.size()) + self.assertEquals(stack.size(), pyjlist.size()) + array = pyjlist.toArray() + + def test_return_types(self): + + # Void + sig = signature(jvoid, []) + self.assertEquals(sig, "()V") + + # Boolean + sig = signature(jboolean, []) + self.assertEquals(sig, "()Z") + + # Byte + sig = signature(jbyte, []) + self.assertEquals(sig, "()B") + + # Char + sig = signature(jchar, []) + self.assertEquals(sig, "()C") + + # Double + sig = signature(jdouble, []) + self.assertEquals(sig, "()D") + + # Float + sig = signature(jfloat, []) + self.assertEquals(sig, "()F") + + # Int + sig = signature(jint, []) + self.assertEquals(sig, "()I") + + # Long + sig = signature(jlong, []) + self.assertEquals(sig, "()J") + + # Short + sig = signature(jshort, []) + self.assertEquals(sig, "()S") + + # Object return method + String = autoclass("java.lang.String") + sig = signature(String, []) + self.assertEquals(sig, "()Ljava/lang/String;") + + # Array return + sig = signature(JArray(jint), []) + self.assertEquals(sig, "()[I") + + def test_params(self): + String = autoclass("java.lang.String") + + # Return void, takes objects as parameters + sig = signature(jvoid, [String, String]) + self.assertEquals(sig, "(Ljava/lang/String;Ljava/lang/String;)V") + + # Multiple array parameter types + sig = signature(jvoid, [JArray(jint), JArray(jboolean)]) + self.assertEquals(sig, "([I[Z)V") + diff --git a/tests/test_simple.py b/tests/test_simple.py index 2601562..edfcd99 100644 --- a/tests/test_simple.py +++ b/tests/test_simple.py @@ -1,12 +1,15 @@ +from __future__ import print_function +from __future__ import division +from __future__ import absolute_import import unittest from jnius import JavaClass, MetaJavaClass, JavaMethod +from six import with_metaclass class HelloWorldTest(unittest.TestCase): def test_helloworld(self): - class HelloWorld(JavaClass): - __metaclass__ = MetaJavaClass + class HelloWorld(with_metaclass(MetaJavaClass, JavaClass)): __javaclass__ = 'org/jnius/HelloWorld' hello = JavaMethod('()Ljava/lang/String;')