├── .github └── workflows │ ├── ci.yml │ ├── publish.yml │ └── release.yml ├── .gitignore ├── CONTRIBUTING.md ├── LICENSE ├── MANIFEST.in ├── Makefile ├── README.rst ├── changes ├── .gitignore ├── 32.bugfix.rst ├── 72.bugfix.rst ├── 76.bugfix.rst ├── 77.misc.rst └── template.rst ├── docs ├── Makefile ├── _static │ └── images │ │ └── rubicon.png ├── background │ ├── community.rst │ ├── faq.rst │ ├── index.rst │ ├── releases.rst │ ├── roadmap.rst │ └── success.rst ├── conf.py ├── how-to │ ├── contribute-code.rst │ ├── contribute-docs.rst │ ├── get-started.rst │ └── index.rst ├── index.rst ├── make.bat ├── reference │ └── index.rst ├── requirements_docs.txt ├── spelling_wordlist └── tutorial │ ├── index.rst │ ├── tutorial-1.rst │ └── tutorial-2.rst ├── jni ├── rubicon.c └── rubicon.h ├── org └── beeware │ └── rubicon │ ├── Python.java │ ├── PythonInstance.java │ └── test │ ├── AbstractCallback.java │ ├── AddOne.java │ ├── BaseExample.java │ ├── Example.java │ ├── ICallback.java │ ├── ICallbackBool.java │ ├── ICallbackInt.java │ ├── Test.java │ ├── Thing.java │ └── TruthInverter.java ├── pyproject.toml ├── rubicon └── java │ ├── __init__.py │ ├── android_events.py │ ├── api.py │ ├── jni.py │ └── types.py ├── setup.cfg ├── setup.py ├── tests ├── __init__.py ├── runner.py ├── test_jni.py └── test_rubicon.py └── tox.ini /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | # Test rubicon-java on all supported operating systems & Python versions. 2 | # 3 | # First test on Python 3.6 on Ubuntu & Mac, then the rest. This is a load 4 | # management strategy: in sprint situations, it's faster to run the tests on 5 | # one platform and confirm they're likely to pass elsewhere, rather than start 6 | # many parallel builds which will all fail for the same daft reason. Our smoke 7 | # test runs on both Linux and macOS to avoid bookkeeping about macOS and Linux 8 | # needing to test on different Python versions. 9 | name: CI 10 | on: 11 | pull_request: 12 | push: 13 | branches: 14 | - main 15 | 16 | jobs: 17 | beefore: 18 | name: Pre-test checks 19 | runs-on: ubuntu-latest 20 | strategy: 21 | max-parallel: 4 22 | matrix: 23 | task: 24 | - 'flake8' 25 | - 'towncrier-check' 26 | - 'docs' 27 | - 'package' 28 | steps: 29 | - uses: actions/checkout@v1 30 | - name: Set up Python 31 | uses: actions/setup-python@v2 32 | with: 33 | python-version: '3.X' 34 | - name: Install dependencies 35 | run: | 36 | python -m pip install --upgrade pip 37 | python -m pip install --upgrade setuptools 38 | python -m pip install tox 39 | - name: Run pre-test check 40 | run: | 41 | tox -e ${{ matrix.task }} 42 | 43 | smoke: 44 | needs: [beefore] 45 | name: Smoke test 46 | strategy: 47 | matrix: 48 | os: [ubuntu-18.04, macOS-latest] 49 | python-version: ["3.7"] 50 | runs-on: ${{ matrix.os }} 51 | steps: 52 | - name: Environment - checkout code 53 | uses: actions/checkout@main 54 | - name: Environment - Setup python 55 | uses: actions/setup-python@v2 56 | with: 57 | python-version: ${{ matrix.python-version }} 58 | - name: Environment - Use Java 8 59 | uses: actions/setup-java@v1 60 | with: 61 | java-version: 8 62 | - name: Install dependencies 63 | run: | 64 | python -m pip install --upgrade pip 65 | python -m pip install --upgrade setuptools 66 | python -m pip install tox 67 | - name: Test 68 | # For the Python 3.5 and 3.7 distributions on macOS in GitHub Actions, 69 | # python-config --ldflags refuses to print a -L directive. We work 70 | # around that with the LIBRARY_PATH variable below. 71 | run: | 72 | LIBRARY_PATH="${{ env.pythonLocation }}/lib" tox -e py 73 | 74 | 75 | # Now test on all recent versions of Python as well. 76 | python-versions: 77 | needs: [smoke] 78 | name: Python compatibility test 79 | strategy: 80 | max-parallel: 4 81 | matrix: 82 | python-version: ["3.8", "3.9", "3.10", "3.11.0-alpha - 3.11.0"] 83 | os: [ubuntu-18.04, macOS-latest] 84 | runs-on: ${{ matrix.os }} 85 | steps: 86 | - name: Environment - checkout code 87 | uses: actions/checkout@main 88 | - name: Environment - Setup python 89 | uses: actions/setup-python@v2 90 | with: 91 | python-version: ${{ matrix.python-version }} 92 | - name: Environment - Use Java 8 93 | uses: actions/setup-java@v1 94 | with: 95 | java-version: 8 96 | - name: Install dependencies 97 | run: | 98 | python -m pip install --upgrade pip 99 | python -m pip install --upgrade setuptools 100 | python -m pip install tox 101 | - name: Test 102 | # For the Python 3.5 and 3.7 distributions on macOS in GitHub Actions, 103 | # python-config --ldflags refuses to print a -L directive. We work 104 | # around that with the LIBRARY_PATH variable below. 105 | run: | 106 | LIBRARY_PATH="${{ env.pythonLocation }}/lib" tox -e py 107 | 108 | # Test on x86 (32-bit) Ubuntu 18.04, focusing on 32-bit/64-bit errors; 109 | # just one Python version. 110 | x86: 111 | needs: [smoke] 112 | name: x86 32-bit tests 113 | runs-on: ubuntu-latest 114 | steps: 115 | - name: Environment - checkout code 116 | uses: actions/checkout@main 117 | - name: Install dependencies, compile rubicon-java, and run tests 118 | uses: docker://i386/ubuntu:18.04 119 | # This step does everything because changes within the container 120 | # (e.g., apt-get) may be discarded between steps. 121 | # Inspired by: https://github.community/t5/GitHub-Actions/Workflow-fails-when-running-steps-in-a-container/td-p/29652 122 | # We need to pass in PYTHON_CONFIG because Ubuntu doesn't define 123 | # python3 as an alias for python3.7 by default, so the Makefile 124 | # can't autodetect that it needs to use python3.7-config. 125 | with: 126 | entrypoint: /bin/sh 127 | args: -c "apt-get update && apt-get install -y build-essential python3.7-dev openjdk-8-jdk-headless && JAVA_HOME=/usr/lib/jvm/java-8-openjdk-i386/ PYTHON_CONFIG=python3.7-config make && JAVA_HOME=/usr/lib/jvm/java-8-openjdk-i386/ RUBICON_LIBRARY=build/librubicon.so LD_LIBRARY_PATH=./build java org.beeware.rubicon.test.Test" 128 | -------------------------------------------------------------------------------- /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | name: Upload Python Package 2 | 3 | on: 4 | release: 5 | types: published 6 | 7 | jobs: 8 | deploy: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - uses: actions/checkout@v1 12 | - name: Set up Python 13 | uses: actions/setup-python@v1 14 | with: 15 | python-version: '3.x' 16 | - name: Install dependencies 17 | run: | 18 | python -m pip install --upgrade pip 19 | pip install --upgrade setuptools wheel twine 20 | - name: Build and publish 21 | env: 22 | TWINE_USERNAME: __token__ 23 | TWINE_PASSWORD: ${{ secrets.PYPI_PASSWORD }} 24 | run: | 25 | python setup.py sdist bdist_wheel 26 | twine check dist/* 27 | twine upload dist/* 28 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Create Release 2 | 3 | on: 4 | push: 5 | tags: 6 | - 'v*' 7 | 8 | jobs: 9 | build: 10 | name: Create Release 11 | runs-on: ubuntu-latest 12 | steps: 13 | - name: Checkout code 14 | uses: actions/checkout@master 15 | - name: Create Release 16 | id: create_release 17 | uses: actions/create-release@v1 18 | env: 19 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 20 | with: 21 | tag_name: ${{ github.ref }} 22 | release_name: ${{ github.ref }} 23 | draft: true 24 | prerelease: false 25 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | *~ 3 | .*.sw[op] 4 | *.egg-info 5 | *.class 6 | *.o 7 | .coverage 8 | dist 9 | build 10 | _build 11 | distribute-* 12 | local 13 | .tox 14 | venv 15 | .vscode 16 | pip-wheel-metadata/ 17 | .envrc 18 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | BeeWare <3's contributions! 4 | 5 | Please be aware, BeeWare operates under a Code of Conduct. 6 | 7 | See [CONTRIBUTING to BeeWare](http://beeware.org/contributing) for details. 8 | 9 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014 Russell Keith-Magee. 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without modification, 5 | are permitted provided that the following conditions are met: 6 | 7 | 1. Redistributions of source code must retain the above copyright notice, 8 | this list of conditions and the following disclaimer. 9 | 10 | 2. Redistributions in binary form must reproduce the above copyright 11 | notice, this list of conditions and the following disclaimer in the 12 | documentation and/or other materials provided with the distribution. 13 | 14 | 3. Neither the name of Rubicon nor the names of its contributors may 15 | be used to endorse or promote products derived from this software without 16 | specific prior written permission. 17 | 18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 19 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 20 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 22 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 23 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 24 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 25 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 27 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include CONTRIBUTING.md 2 | include LICENSE 3 | include Makefile 4 | include README.rst 5 | include pyproject.toml 6 | include tox.ini 7 | recursive-include changes *.rst 8 | include docs/Makefile 9 | include docs/make.bat 10 | include docs/requirements_docs.txt 11 | include docs/spelling_wordlist 12 | recursive-include docs *.png *.py *.rst 13 | prune docs/_build 14 | recursive-include jni *.h *.c *.mk 15 | recursive-include org *.java 16 | recursive-include tests *.py 17 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # If the user specifies PYTHON_CONFIG in their environment, use that value. 2 | # Otherwise, look for a viable python-config in their environment. 3 | # In *most* environments, this will be python3.X-config in the same directory 4 | # as the actual Python binary. However, under some virtual environments 5 | # conditions, this binary will be called `python-config`. Look for both names. 6 | ifndef PYTHON_CONFIG 7 | PYTHON_EXE := $(shell python3 -c "import sys; from pathlib import Path; print(str(Path(sys.executable).resolve()))") 8 | PYTHON_CONFIG := $(PYTHON_EXE)-config 9 | ifneq ($(shell test -e $(PYTHON_CONFIG) && echo exists),exists) 10 | PYTHON_CONFIG := $(shell dirname $(PYTHON_EXE))/python-config 11 | endif 12 | endif 13 | 14 | # Optionally read C compiler from the environment. 15 | ifndef CC 16 | CC := gcc 17 | endif 18 | 19 | # Compute Python version + ABI suffix string by looking for the embeddable 20 | # library name from the output of python-config. This way, we avoid executing 21 | # the Python interpreter, which helps us in cross-compile contexts (e.g., 22 | # Python built for Android). 23 | PYTHON_LDVERSION := $(shell ($(PYTHON_CONFIG) --libs || true 2>&1 ) | cut -d ' ' -f1 | grep python | sed s,-lpython,, ) 24 | PYTHON_CONFIG_EXTRA_FLAGS := "" 25 | # If that didn't give us a Python library name, then we're on Python 3.8 or 26 | # higher, and we have to pass --embed. See 27 | # https://docs.python.org/3/whatsnew/3.8.html#debug-build-uses-the-same-abi-as-release-build 28 | ifndef PYTHON_LDVERSION 29 | PYTHON_CONFIG_EXTRA_FLAGS := "--embed" 30 | PYTHON_LDVERSION := $(shell ($(PYTHON_CONFIG) --libs ${PYTHON_CONFIG_EXTRA_FLAGS} || true 2>&1 ) | cut -d ' ' -f1 | grep python | sed s,-lpython,, ) 31 | endif 32 | 33 | PYTHON_VERSION := $(shell echo ${PYTHON_LDVERSION} | sed 's,[^0-9.],,g') 34 | 35 | # Use CFLAGS and LDFLAGS based on Python's. We add -fPIC since we're creating a 36 | # shared library, and we remove -stack_size (only seen on macOS), since it only 37 | # applies to executables. 38 | # -Wno-nullability-completeness and -Wno-expansion-to-defined silence warnings that 39 | # are raised by the C library itself. 40 | CFLAGS := $(shell $(PYTHON_CONFIG) --cflags) -fPIC -Wno-nullability-completeness -Wno-expansion-to-defined 41 | LDFLAGS := $(shell $(PYTHON_CONFIG) --ldflags ${PYTHON_CONFIG_EXTRA_FLAGS} | sed 'sX-Wl,-stack_size,1000000XXg') 42 | 43 | # If we are compiling for Android, the C code will detect it via #define. We need to accommodate 44 | # that by linking to the Android "log" library. 45 | IS_ANDROID := $(shell $(CC) -dM -E - < /dev/null | grep -q __ANDROID__ && echo yes || echo no) 46 | ifeq ($(IS_ANDROID),yes) 47 | LDFLAGS := $(LDFLAGS) -llog 48 | endif 49 | 50 | # When compiling on macOS, check for M1/Apple Silicon. 51 | IS_APPLE_SILICON := $(shell uname -ms | grep -q "Darwin arm64" && echo yes || echo no) 52 | ifeq ($(IS_APPLE_SILICON),yes) 53 | CFLAGS := $(CFLAGS) -arch x86_64 54 | LDFLAGS := $(LDFLAGS) -arch x86_64 55 | endif 56 | 57 | ifdef JAVA_HOME 58 | JAVAC := $(JAVA_HOME)/bin/javac 59 | else 60 | JAVAC := $(shell which javac) 61 | ifeq ($(wildcard /usr/libexec/java_home),) 62 | JAVA_HOME := $(shell realpath $(JAVAC)) 63 | else 64 | JAVA_HOME := $(shell /usr/libexec/java_home) 65 | endif 66 | endif 67 | 68 | # Rely on the current operating system to decide which JNI headers to use, and 69 | # for one Python flag. At the moment, this means that Android builds of rubicon-java 70 | # must be done on a Linux host. 71 | LOWERCASE_OS := $(shell uname -s | tr '[A-Z]' '[a-z]') 72 | JAVA_PLATFORM := $(JAVA_HOME)/include/$(LOWERCASE_OS) 73 | ifeq ($(LOWERCASE_OS),linux) 74 | SOEXT := so 75 | # On Linux, including Android, Python extension modules require that `rubicon-java` dlopen() libpython.so with RTLD_GLOBAL. 76 | # Pass enough information here to allow that to happen. 77 | CFLAGS += -DLIBPYTHON_RTLD_GLOBAL=\"libpython${PYTHON_LDVERSION}.so\" 78 | else ifeq ($(LOWERCASE_OS),darwin) 79 | SOEXT := dylib 80 | endif 81 | 82 | all: build/rubicon.jar build/librubicon.$(SOEXT) build/test.jar 83 | 84 | build/rubicon.jar: org/beeware/rubicon/Python.class org/beeware/rubicon/PythonInstance.class 85 | mkdir -p build 86 | jar -cvf build/rubicon.jar org/beeware/rubicon/Python.class org/beeware/rubicon/PythonInstance.class 87 | 88 | build/test.jar: org/beeware/rubicon/test/BaseExample.class org/beeware/rubicon/test/Example.class org/beeware/rubicon/test/ICallback.class org/beeware/rubicon/test/ICallbackBool.class org/beeware/rubicon/test/ICallbackInt.class org/beeware/rubicon/test/AddOne.class org/beeware/rubicon/test/TruthInverter.class org/beeware/rubicon/test/AbstractCallback.class org/beeware/rubicon/test/Thing.class org/beeware/rubicon/test/Test.class 89 | mkdir -p build 90 | jar -cvf build/test.jar org/beeware/rubicon/test/*.class 91 | 92 | build/librubicon.$(SOEXT): jni/rubicon.o 93 | mkdir -p build 94 | $(CC) -shared -o $@ $< $(LDFLAGS) 95 | 96 | PYTHON_LIBS_DIR := $(shell echo `dirname $(PYTHON_CONFIG)`/../lib) 97 | 98 | test: all 99 | # Rather than test which OS we're on, we set the Mac DYLD_LIBRARY_PATH as well 100 | # as the the LD_LIBRARY_PATH variable seen on Linux. Additionally, the Mac 101 | # variable seems to get stripped when running some tools, so it's helpful to 102 | # add it here rather than ask the user to set it in their environment. 103 | DYLD_LIBRARY_PATH=$(PYTHON_LIBS_DIR) \ 104 | LD_LIBRARY_PATH=$(PYTHON_LIBS_DIR) \ 105 | RUBICON_LIBRARY=$(shell ls ./build/librubicon.*) \ 106 | java -Djava.library.path="./build" org.beeware.rubicon.test.Test 107 | 108 | clean: 109 | rm -f org/beeware/rubicon/test/*.class 110 | rm -f org/beeware/rubicon/*.class 111 | rm -f jni/*.o 112 | rm -rf build 113 | 114 | %.class : %.java 115 | $(JAVAC) $< 116 | 117 | %.o : %.c 118 | $(CC) -c $(CFLAGS) -Isrc -I$(JAVA_HOME)/include -I$(JAVA_PLATFORM) -o $@ $< 119 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | **This project has been archived. It was used by Briefcase 0.3.9 and earlier to 2 | support Android projects; however, that role is now performed by `Chaquopy 3 | `__.** 4 | 5 | .. image:: https://beeware.org/project/projects/bridges/rubicon/rubicon.png 6 | :width: 72px 7 | :target: https://beeware.org/rubicon 8 | 9 | Rubicon-Java 10 | ============ 11 | 12 | .. image:: https://img.shields.io/pypi/pyversions/rubicon-java.svg 13 | :target: https://pypi.python.org/pypi/rubicon-java 14 | :alt: Python Versions 15 | 16 | .. image:: https://img.shields.io/pypi/v/rubicon-java.svg 17 | :target: https://pypi.python.org/pypi/rubicon-java 18 | :alt: Project version 19 | 20 | .. image:: https://img.shields.io/pypi/status/rubicon-java.svg 21 | :target: https://pypi.python.org/pypi/rubicon-java 22 | :alt: Project status 23 | 24 | .. image:: https://img.shields.io/pypi/l/rubicon-java.svg 25 | :target: https://github.com/beeware/rubicon-java/blob/master/LICENSE 26 | :alt: License 27 | 28 | .. image:: https://github.com/beeware/rubicon-java/workflows/CI/badge.svg?branch=master 29 | :target: https://github.com/beeware/rubicon-java/actions 30 | :alt: Build Status 31 | 32 | .. image:: https://img.shields.io/discord/836455665257021440?label=Discord%20Chat&logo=discord&style=plastic 33 | :target: https://beeware.org/bee/chat/ 34 | :alt: Discord server 35 | 36 | Rubicon-Java is a bridge between the Java Runtime Environment and Python. 37 | It enables you to: 38 | 39 | * Instantiate objects defined in Java, 40 | * Invoke static and instance methods on objects defined in Java, 41 | * Access and modify static and instance fields on objects defined in Java, and 42 | * Write and use Python implementations of interfaces defined in Java. 43 | 44 | Quickstart 45 | ---------- 46 | 47 | Rubicon-Java consists of three components: 48 | 49 | 1. A Python library, 50 | 2. A JNI library, and 51 | 3. A Java JAR file. 52 | 53 | A ``Makefile`` has been provided to compile the JNI and JAR components. Type:: 54 | 55 | $ make 56 | 57 | to compile them. The compiled output will be placed in the ``build`` directory. 58 | 59 | To use Rubicon-Java, you'll need to ensure: 60 | 61 | 1. ``rubicon.jar`` is in the classpath when you start your Java VM. 62 | 63 | 2. The Rubicon library file is somewhere that it will be found by dynamic 64 | library discovery. This means: 65 | 66 | a. Under OS X, put the directory containing ``librubicon.dylib`` is in your ``DYLD_LIBRARY_PATH`` 67 | 68 | b. Under Linux, put the directory containing ``librubicon.so`` is in your ``LD_LIBRARY_PATH`` 69 | 70 | c. Under Windows.... something :-) 71 | 72 | 3. The ``rubicon`` Python module is somewhere that can be added to a 73 | ``PYTHONPATH``. You can install rubicon using:: 74 | 75 | $ pip install rubicon-java 76 | 77 | If you do this, you'll need to reference your system Python install when 78 | setting your ``PYTHONPATH``. 79 | 80 | The Rubicon bridge starts on the Java side. Import the Python object:: 81 | 82 | import org.beeware.rubicon.Python; 83 | 84 | Then start the Python interpreter, and run a Python file:: 85 | 86 | # Initialize the Python VM 87 | String pythonHome = "/path/to/python"; 88 | String pythonPath = "/path/to/dir1:/path/to/dir2"; 89 | if (Python.start(pythonHome, pythonPath, null) != 0) { 90 | System.out.println("Error initializing Python VM."); 91 | } 92 | 93 | # Start a Python module 94 | if (Python.run("path.of.module") != 0) { 95 | System.out.println("Error running Python script."); 96 | } 97 | 98 | # Shut down the Python VM. 99 | Python.stop(); 100 | 101 | The ``PYTHONPATH`` you specify must enable access to the ``rubicon`` Python 102 | module. 103 | 104 | In your Python script, you can then reference Java objects:: 105 | 106 | >>> from rubicon.java import JavaClass 107 | 108 | # Wrap a Java class 109 | >>> URL = JavaClass("java/net/URL") 110 | 111 | # Then instantiate the Java class, using the API 112 | # that is exposed in Java. 113 | >>> url = URL("https://beeware.org") 114 | 115 | # You can then call methods on the Java object as if it 116 | # were a Python object. 117 | >>> print(url.getHost()) 118 | beeware.org 119 | 120 | It's also possible to provide implementations of Java Interfaces in Python. 121 | For example, lets say you want to create a Swing Button, and you want to 122 | respond to button clicks:: 123 | 124 | >>> from rubicon.java import JavaClass, JavaInterface 125 | 126 | # Wrap the Java interface 127 | >>> ActionListener = JavaInterface('java/awt/event/ActionListener') 128 | 129 | # Define your own implementation 130 | >>> class MyActionListener(ActionListener): 131 | ... def actionPerformed(self, event): 132 | ... print("Button Pressed") 133 | 134 | # Instantiate an instance of the listener 135 | >>> listener = MyActionListener() 136 | 137 | # Create a button, and set the listener 138 | >>> Button = JavaClass('javax/swing/JButton') 139 | >>> button = Button('Push it') 140 | >>> button.setActionListener(listener) 141 | 142 | Of course, this sample code won't work unless it's in the context of a larger 143 | application starting a Swing GUI and so on. 144 | 145 | Testing 146 | ------- 147 | 148 | To run the Rubicon test suite: 149 | 150 | 1. Ensure that ``java`` is on your ``$PATH``, or set the ``JAVA_HOME`` environment 151 | variable to point to a directory of a Java Development Kit (JDK). 152 | 153 | 2. Create a Python 3 virtual environment, and ensure that pip & setuptools are 154 | up to date:: 155 | 156 | $ python3 -m venv venv 157 | $ source venv/bin/activate 158 | (venv) $ python -m pip install --upgrade pip 159 | (venv) $ python -m pip install --upgrade setuptools 160 | 161 | 3. Install ``tox``:: 162 | 163 | (venv) $ python -m pip install tox 164 | 165 | 4. Run the test suite. The following should work properly on both macOS and 166 | Linux:: 167 | 168 | (venv) $ tox -e py 169 | 170 | This will compile the Rubicon library, compile the Java test classes, and 171 | run the Python test suite from within the Java environment. 172 | 173 | Documentation 174 | ------------- 175 | 176 | Full documentation for Rubicon can be found on `Read The Docs`_. 177 | 178 | Community 179 | --------- 180 | 181 | Rubicon is part of the `BeeWare suite`_. You can talk to the community through: 182 | 183 | * `@PyBeeWare on Twitter`_ 184 | 185 | * The `beeware/general`_ channel on Gitter. 186 | 187 | We foster a welcoming and respectful community as described in our 188 | `BeeWare Community Code of Conduct`_. 189 | 190 | Contributing 191 | ------------ 192 | 193 | If you experience problems with this backend, `log them on GitHub`_. If you 194 | want to contribute code, please `fork the code`_ and `submit a pull request`_. 195 | 196 | .. _BeeWare suite: http://beeware.org 197 | .. _Read The Docs: http://rubicon-java.readthedocs.org 198 | .. _@PyBeeWare on Twitter: https://twitter.com/PyBeeWare 199 | .. _beeware/general: https://gitter.im/beeware/general 200 | .. _BeeWare Community Code of Conduct: http://beeware.org/community/behavior/ 201 | .. _log them on Github: https://github.com/beeware/rubicon-java/issues 202 | .. _fork the code: https://github.com/beeware/rubicon-java 203 | .. _submit a pull request: https://github.com/beeware/rubicon-java/pulls 204 | -------------------------------------------------------------------------------- /changes/.gitignore: -------------------------------------------------------------------------------- 1 | !.gitignore 2 | -------------------------------------------------------------------------------- /changes/32.bugfix.rst: -------------------------------------------------------------------------------- 1 | When the test suite completes successfully, it no longer outputs a message suggesting failure. -------------------------------------------------------------------------------- /changes/72.bugfix.rst: -------------------------------------------------------------------------------- 1 | Modified test suite to support M1/Apple Silicon. -------------------------------------------------------------------------------- /changes/76.bugfix.rst: -------------------------------------------------------------------------------- 1 | Asynchronous co-routines clean up their handler callbacks when being re-queued. Without this cleanup, every time an co-routine was awaited, a no-op invocation of the handler would occur. -------------------------------------------------------------------------------- /changes/77.misc.rst: -------------------------------------------------------------------------------- 1 | A reference to rubicon-objc was corrected. -------------------------------------------------------------------------------- /changes/template.rst: -------------------------------------------------------------------------------- 1 | {% if top_line %} 2 | {{ top_line }} 3 | {{ top_underline * ((top_line)|length)}} 4 | {% elif versiondata.name %} 5 | {{ versiondata.name }} {{ versiondata.version }} ({{ versiondata.date }}) 6 | {{ top_underline * ((versiondata.name + versiondata.version + versiondata.date)|length + 4)}} 7 | {% else %} 8 | {{ versiondata.version }} ({{ versiondata.date }}) 9 | {{ top_underline * ((versiondata.version + versiondata.date)|length + 3)}} 10 | {% endif %} 11 | {% for section, _ in sections.items() %} 12 | {% set underline = underlines[0] %}{% if section %}{{section}} 13 | {{ underline * section|length }}{% set underline = underlines[1] %} 14 | 15 | {% endif %} 16 | 17 | {% if sections[section] %} 18 | {% for category, val in definitions.items() if category in sections[section]%} 19 | {{ definitions[category]['name'] }} 20 | {{ underline * definitions[category]['name']|length }} 21 | 22 | {% if definitions[category]['showcontent'] %} 23 | {% for text, values in sections[section][category].items() %} 24 | * {{ text }} ({{ values|join(', ') }}) 25 | {% endfor %} 26 | 27 | {% else %} 28 | * {{ sections[section][category]['']|join(', ') }} 29 | 30 | {% endif %} 31 | {% if sections[section][category]|length == 0 %} 32 | No significant changes. 33 | 34 | {% else %} 35 | {% endif %} 36 | 37 | {% endfor %} 38 | {% else %} 39 | No significant changes. 40 | 41 | {% endif %} 42 | {% endfor %} 43 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Minimal makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line, and also 5 | # from the environment for the first two. 6 | SPHINXOPTS ?= 7 | SPHINXBUILD ?= sphinx-build 8 | SOURCEDIR = . 9 | BUILDDIR = _build 10 | # Put it first so that "make" without argument is like "make help". 11 | 12 | help: 13 | @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 14 | 15 | .PHONY: help livehtml Makefile 16 | 17 | livehtml: 18 | sphinx-autobuild -b html $(SPHINXOPTS) "$(SOURCEDIR)" "$(BUILDDIR)/html" 19 | 20 | # Catch-all target: route all unknown targets to Sphinx using the new 21 | # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). 22 | %: Makefile 23 | @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 24 | -------------------------------------------------------------------------------- /docs/_static/images/rubicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/beeware/rubicon-java/2d3842dbf048faf19536cf8a580c338d76943dda/docs/_static/images/rubicon.png -------------------------------------------------------------------------------- /docs/background/community.rst: -------------------------------------------------------------------------------- 1 | ============================================= 2 | The Rubicon Java Developer and User community 3 | ============================================= 4 | 5 | Rubicon Java is part of the `BeeWare suite`_. You can talk to the 6 | community through: 7 | 8 | * `@pybeeware on Twitter`_ 9 | 10 | * The `beeware/general`_ channel on Gitter. 11 | 12 | Code of Conduct 13 | --------------- 14 | 15 | The BeeWare community has a strict `Code of Conduct`_. All users and 16 | developers are expected to adhere to this code. 17 | 18 | If you have any concerns about this code of conduct, or you wish to report a 19 | violation of this code, please contact the project founder `Russell Keith-Magee`_. 20 | 21 | Contributing 22 | ------------ 23 | 24 | If you experience problems with Rubicon, `log them on GitHub`_. If you 25 | want to contribute code, please `fork the code`_ and `submit a pull request`_. 26 | 27 | .. _BeeWare suite: http://beeware.org 28 | .. _Read The Docs: https://rubicon-java.readthedocs.io 29 | .. _@pybeeware on Twitter: https://twitter.com/pybeeware 30 | .. _beeware/general: https://gitter.im/beeware/general 31 | .. _log them on Github: https://github.com/beeware/rubicon-java/issues 32 | .. _fork the code: https://github.com/beeware/rubicon-java 33 | .. _submit a pull request: https://github.com/beeware/rubicon-java/pulls 34 | 35 | .. _Code of Conduct: http://beeware.org/contributing/index.html 36 | .. _Russell Keith-Magee: mailto:russell@keith-magee.com 37 | -------------------------------------------------------------------------------- /docs/background/faq.rst: -------------------------------------------------------------------------------- 1 | ============== 2 | Why "Rubicon"? 3 | ============== 4 | 5 | So... why the name Rubicon? 6 | =========================== 7 | 8 | The Rubicon is a river in Italy. It was of importance in ancient times as the 9 | border of Rome. The Roman Army was prohibited from crossing this border, as that 10 | would be considered a hostile act against the Roman Senate. 11 | 12 | In 54 BC, Julius Caesar marched the Roman Army across the Rubicon, signaling 13 | his intention to overthrow the Roman Senate. As he did so, legend says he 14 | uttered the words "Alea Iacta Est" - The die is cast. This action led to Julius 15 | being crowned as Emperor of Rome, and the start of the Roman Empire. 16 | 17 | Of course, in order to cross any river, you need to use a bridge. 18 | 19 | This project provides a bridge between the open world of the Python 20 | ecosystem, and the Java ecosystem. 21 | -------------------------------------------------------------------------------- /docs/background/index.rst: -------------------------------------------------------------------------------- 1 | ========== 2 | Background 3 | ========== 4 | 5 | Want to know more about the Rubicon project, it's history, community, and 6 | plans for the future? That's what you'll find here! 7 | 8 | .. toctree:: 9 | :maxdepth: 1 10 | 11 | faq 12 | community 13 | success 14 | releases 15 | roadmap 16 | -------------------------------------------------------------------------------- /docs/background/releases.rst: -------------------------------------------------------------------------------- 1 | =============== 2 | Release History 3 | =============== 4 | 5 | .. towncrier release notes start 6 | 7 | 0.2.6 (2022-01-06) 8 | ================== 9 | 10 | Features 11 | -------- 12 | 13 | * Added support for arrays in arguments and return values. (#55) 14 | * Added the ability to cast Java objects from one class to another. (#58) 15 | * Local JNI Instances can be converted into global JNI instances using ``__global__()`` (#59) 16 | * Added support for Python 3.9 and 3.10. (#62, #66) 17 | * Methods that accept a Java ``NULL`` as an argument are now supported. (#63) 18 | * Methods that return ``NULL`` now return typed ``NULL`` objects. (#67) 19 | 20 | 0.2.5 (2021-01-05) 21 | ================== 22 | 23 | Features 24 | -------- 25 | 26 | * Added support for passing Python lists into Java numeric array types (#53) 27 | 28 | 29 | 0.2.4 (2020-08-06) 30 | ================== 31 | 32 | Features 33 | -------- 34 | 35 | * Added support for implementing Java interfaces that return ``bool``. (#52) 36 | 37 | 38 | 0.2.3 (2020-07-27) 39 | ================== 40 | 41 | Bugfixes 42 | -------- 43 | 44 | * The asyncio event loop can now start on Python 3.6. (#51) 45 | 46 | 47 | 0.2.2 (2020-07-03) 48 | ================== 49 | 50 | Features 51 | -------- 52 | 53 | * Python's AsyncIO event loop is now integrated with the Android event loop. 54 | (#40, #49) 55 | * ``sys.stdout`` & ``sys.stderr`` are now routed to the Android debug log using 56 | an ``android`` Python module. (#44) 57 | 58 | Misc 59 | ---- 60 | 61 | * #45, #46, #47, #48 62 | 63 | 64 | 0.2.1 (2020-06-17) 65 | ================== 66 | 67 | Features 68 | -------- 69 | 70 | * Add the ability to implement Java interface methods that return `int`. (#42) 71 | 72 | Misc 73 | ---- 74 | 75 | * #31, #37 76 | 77 | 78 | 0.2.1 (2020-06-17) 79 | ================== 80 | 81 | Features 82 | -------- 83 | 84 | * Add the ability to implement Java interface methods that return `int`. (#42) 85 | 86 | Misc 87 | ---- 88 | 89 | * #31, #37 90 | 91 | 92 | 0.2.0 93 | ===== 94 | 95 | Changes since v0.1.0: 96 | 97 | - Port to Python 3, removing Python 2 support. 98 | - Add support to Linux, allowing it to run on both macOS and Linux. 99 | - Enable cross-compiling by adding support in the ``Makefile`` for specifying the compiler to use, the Python version whose headers to use, and to not require executing Python code during the build process. 100 | - Adjust ``Python.run()`` to take a module name, not a filename. 101 | - Add more documentation, although some is skeletal. Improvements to this are welcome. 102 | - Add towncrier_ support. Future releases will rely on towncrier for release notes. 103 | - Rename ``pybee`` to ``beeware``. 104 | - Add GitHub Actions configuration for testing on macOS & Linux and Python 3.5, 3.6, 3.7, and 3.8. 105 | - Support the ``JAVA_HOME`` environment variable to choose a Java compiler. 106 | 107 | This version specifically targets Java 8 to simplify Android support. 108 | 109 | Thanks to the contributors, listed by GitHub username in alphabetical order: 110 | 111 | - @freakboy3742 112 | - @glasnt 113 | - @jacebrowning 114 | - @paulproteus 115 | - @RomanKharin 116 | 117 | .. _towncrier: https://pypi.org/project/towncrier/ 118 | -------------------------------------------------------------------------------- /docs/background/roadmap.rst: -------------------------------------------------------------------------------- 1 | =============== 2 | Rubicon Roadmap 3 | =============== 4 | -------------------------------------------------------------------------------- /docs/background/success.rst: -------------------------------------------------------------------------------- 1 | =============== 2 | Success Stories 3 | =============== 4 | 5 | Want to see examples of Rubicon in use? Here's some: 6 | -------------------------------------------------------------------------------- /docs/conf.py: -------------------------------------------------------------------------------- 1 | # Rubicon documentation build configuration file, created by 2 | # sphinx-quickstart on Sat Jul 27 14:58:42 2013. 3 | # 4 | # This file is execfile()d with the current directory set to its containing dir. 5 | # 6 | # Note that not all possible configuration values are present in this 7 | # autogenerated file. 8 | # 9 | # All configuration values have a default; values that are commented out 10 | # serve to show the default. 11 | 12 | import os 13 | import re 14 | import sys 15 | 16 | # If extensions (or modules to document with autodoc) are in another directory, 17 | # add these directories to sys.path here. If the directory is relative to the 18 | # documentation root, use os.path.abspath to make it absolute, like shown here. 19 | sys.path.insert(0, os.path.abspath('..')) 20 | 21 | # -- General configuration ----------------------------------------------------- 22 | 23 | # If your documentation needs a minimal Sphinx version, state it here. 24 | #needs_sphinx = '1.0' 25 | 26 | # Add any Sphinx extension module names here, as strings. They can be extensions 27 | # coming with Sphinx (named 'sphinx.ext.*') or your custom ones. 28 | extensions = ['sphinx.ext.autodoc', 'sphinx_tabs.tabs'] 29 | 30 | # Add any paths that contain templates here, relative to this directory. 31 | templates_path = ['_templates'] 32 | 33 | # The suffix of source filenames. 34 | source_suffix = '.rst' 35 | 36 | # The encoding of source files. 37 | #source_encoding = 'utf-8-sig' 38 | 39 | # The master toctree document. 40 | master_doc = 'index' 41 | 42 | # General information about the project. 43 | project = 'Rubicon' 44 | copyright = '2014, Russell Keith-Magee' 45 | 46 | # The version info for the project you're documenting, acts as replacement for 47 | # |version| and |release|, also used in various other places throughout the 48 | # built documents. 49 | # 50 | # The full version, including alpha/beta/rc tags. 51 | with open('../rubicon/java/__init__.py', encoding='utf8') as version_file: 52 | version_match = re.search(r"^__version__ = ['\"]([^'\"]*)['\"]", version_file.read(), re.MULTILINE) 53 | if version_match: 54 | release = version_match.group(1) 55 | else: 56 | raise RuntimeError("Unable to find version string.") 57 | 58 | # The short X.Y version. 59 | version = '.'.join(release.split('.')[:2]) 60 | 61 | autoclass_content = 'both' 62 | 63 | # The language for content autogenerated by Sphinx. Refer to documentation 64 | # for a list of supported languages. 65 | #language = None 66 | 67 | # There are two options for replacing |today|: either, you set today to some 68 | # non-false value, then it is used: 69 | #today = '' 70 | # Else, today_fmt is used as the format for a strftime call. 71 | #today_fmt = '%B %d, %Y' 72 | 73 | # List of patterns, relative to source directory, that match files and 74 | # directories to ignore when looking for source files. 75 | exclude_patterns = ['_build'] 76 | 77 | # The reST default role (used for this markup: `text`) to use for all documents. 78 | #default_role = None 79 | 80 | # If true, '()' will be appended to :func: etc. cross-reference text. 81 | #add_function_parentheses = True 82 | 83 | # If true, the current module name will be prepended to all description 84 | # unit titles (such as .. function::). 85 | #add_module_names = True 86 | 87 | # If true, sectionauthor and moduleauthor directives will be shown in the 88 | # output. They are ignored by default. 89 | #show_authors = False 90 | 91 | # The name of the Pygments (syntax highlighting) style to use. 92 | pygments_style = 'sphinx' 93 | 94 | # A list of ignored prefixes for module index sorting. 95 | #modindex_common_prefix = [] 96 | 97 | 98 | # -- Options for HTML output --------------------------------------------------- 99 | 100 | # on_rtd: whether we are on readthedocs.org 101 | on_rtd = os.environ.get('READTHEDOCS', None) == 'True' 102 | 103 | if not on_rtd: # only import and set the theme if we're building docs locally 104 | try: 105 | import sphinx_rtd_theme 106 | except ImportError: 107 | html_theme = 'default' 108 | else: 109 | html_theme = 'sphinx_rtd_theme' 110 | html_theme_path = [sphinx_rtd_theme.get_html_theme_path()] 111 | 112 | # Theme options are theme-specific and customize the look and feel of a theme 113 | # further. For a list of options available for each theme, see the 114 | # documentation. 115 | #html_theme_options = {} 116 | 117 | # Add any paths that contain custom themes here, relative to this directory. 118 | #html_theme_path = [] 119 | 120 | # The name for this set of Sphinx documents. If None, it defaults to 121 | # " v documentation". 122 | #html_title = None 123 | 124 | # A shorter title for the navigation bar. Default is the same as html_title. 125 | #html_short_title = None 126 | 127 | # The name of an image file (relative to this directory) to place at the top 128 | # of the sidebar. 129 | html_logo = "_static/images/rubicon.png" 130 | 131 | # The name of an image file (within the static path) to use as favicon of the 132 | # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 133 | # pixels large. 134 | #html_favicon = None 135 | 136 | # Add any paths that contain custom static files (such as style sheets) here, 137 | # relative to this directory. They are copied after the builtin static files, 138 | # so a file named "default.css" will overwrite the builtin "default.css". 139 | html_static_path = ['_static'] 140 | 141 | # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, 142 | # using the given strftime format. 143 | #html_last_updated_fmt = '%b %d, %Y' 144 | 145 | # If true, SmartyPants will be used to convert quotes and dashes to 146 | # typographically correct entities. 147 | #html_use_smartypants = True 148 | 149 | # Custom sidebar templates, maps document names to template names. 150 | #html_sidebars = {} 151 | 152 | # Additional templates that should be rendered to pages, maps page names to 153 | # template names. 154 | #html_additional_pages = {} 155 | 156 | # If false, no module index is generated. 157 | #html_domain_indices = True 158 | 159 | # If false, no index is generated. 160 | #html_use_index = True 161 | 162 | # If true, the index is split into individual pages for each letter. 163 | #html_split_index = False 164 | 165 | # If true, links to the reST sources are added to the pages. 166 | #html_show_sourcelink = True 167 | 168 | # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. 169 | #html_show_sphinx = True 170 | 171 | # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. 172 | #html_show_copyright = True 173 | 174 | # If true, an OpenSearch description file will be output, and all pages will 175 | # contain a tag referring to it. The value of this option must be the 176 | # base URL from which the finished HTML is served. 177 | #html_use_opensearch = '' 178 | 179 | # This is the file name suffix for HTML files (e.g. ".xhtml"). 180 | #html_file_suffix = None 181 | 182 | # Output file base name for HTML help builder. 183 | htmlhelp_basename = 'rubicondoc' 184 | 185 | try: 186 | import sphinx_rtd_theme 187 | html_theme = "sphinx_rtd_theme" 188 | html_theme_path = [sphinx_rtd_theme.get_html_theme_path()] 189 | except ImportError: 190 | # The sphinx-rtd-theme package is not installed, so to the default 191 | pass 192 | 193 | # -- Options for LaTeX output -------------------------------------------------- 194 | 195 | latex_elements = { 196 | # The paper size ('letterpaper' or 'a4paper'). 197 | #'papersize': 'letterpaper', 198 | 199 | # The font size ('10pt', '11pt' or '12pt'). 200 | #'pointsize': '10pt', 201 | 202 | # Additional stuff for the LaTeX preamble. 203 | #'preamble': '', 204 | } 205 | 206 | # Grouping the document tree into LaTeX files. List of tuples 207 | # (source start file, target name, title, author, documentclass [howto/manual]). 208 | latex_documents = [ 209 | ('index', 'rubicon.tex', 'Rubicon Documentation', 'Russell Keith-Magee', 'manual'), 210 | ] 211 | 212 | # The name of an image file (relative to this directory) to place at the top of 213 | # the title page. 214 | #latex_logo = None 215 | 216 | # For "manual" documents, if this is true, then toplevel headings are parts, 217 | # not chapters. 218 | #latex_use_parts = False 219 | 220 | # If true, show page references after internal links. 221 | #latex_show_pagerefs = False 222 | 223 | # If true, show URL addresses after external links. 224 | #latex_show_urls = False 225 | 226 | # Documents to append as an appendix to all manuals. 227 | #latex_appendices = [] 228 | 229 | # If false, no module index is generated. 230 | #latex_domain_indices = True 231 | 232 | 233 | # -- Options for manual page output -------------------------------------------- 234 | 235 | # One entry per manual page. List of tuples 236 | # (source start file, name, description, authors, manual section). 237 | man_pages = [ 238 | ('index', 'rubicon', 'Rubicon Documentation', ['Russell Keith-Magee'], 1), 239 | ] 240 | 241 | # If true, show URL addresses after external links. 242 | #man_show_urls = False 243 | 244 | 245 | # -- Options for Texinfo output ------------------------------------------------ 246 | 247 | # Grouping the document tree into Texinfo files. List of tuples 248 | # (source start file, target name, title, author, 249 | # dir menu entry, description, category) 250 | texinfo_documents = [ 251 | ( 252 | 'index', 'rubicon', 'Rubicon Documentation', 'Russell Keith-Magee', 'Rubicon', 253 | 'A bridge between the Java Runtime Environment and Python.', 'Miscellaneous', 254 | ), 255 | ] 256 | 257 | # Documents to append as an appendix to all manuals. 258 | #texinfo_appendices = [] 259 | 260 | # If false, no module index is generated. 261 | #texinfo_domain_indices = True 262 | 263 | # How to display URL addresses: 'footnote', 'no', or 'inline'. 264 | #texinfo_show_urls = 'footnote' 265 | 266 | # -- Options for spelling ------------------------------------------- 267 | 268 | # Spelling check needs an additional module that is not installed by default. 269 | # Add it only if spelling check is requested so docs can be generated without it. 270 | if 'spelling' in sys.argv: 271 | extensions.append("sphinxcontrib.spelling") 272 | 273 | # Spelling language. 274 | spelling_lang = 'en_US' 275 | 276 | # Location of word list. 277 | spelling_word_list_filename = 'spelling_wordlist' 278 | 279 | spelling_ignore_pypi_package_names = True 280 | -------------------------------------------------------------------------------- /docs/how-to/contribute-code.rst: -------------------------------------------------------------------------------- 1 | ================================= 2 | How to contribute code to Rubicon 3 | ================================= 4 | 5 | If you experience problems with Rubicon, `log them on GitHub`_. If you want 6 | to contribute code, please `fork the code`_ and `submit a pull request`_. 7 | 8 | .. _log them on Github: https://github.com/beeware/rubicon-java/issues 9 | .. _fork the code: https://github.com/beeware/rubicon-java 10 | .. _submit a pull request: https://github.com/beeware/rubicon-java/pulls 11 | 12 | .. _setup-dev-environment: 13 | 14 | Set up your development environment 15 | =================================== 16 | 17 | The recommended way of setting up your development environment for Rubicon is 18 | to install a virtual environment, install the required dependencies and start 19 | coding: 20 | 21 | .. code-block:: sh 22 | 23 | $ python3 -m venv venv 24 | $ source venv/bin/activate.sh 25 | (venv) $ python -m pip install --upgrade pip 26 | (venv) $ python -m pip install --upgrade setuptools 27 | (venv) $ git clone https://github.com/beeware/rubicon-java.git 28 | (venv) $ cd rubicon-java 29 | (venv) $ python -m pip install -e . 30 | 31 | Rubicon uses tox to describe it's testing environment. To install tox, run: 32 | 33 | .. code-block:: sh 34 | 35 | (venv) $ python -m pip install tox 36 | 37 | You can then run the full test suite: 38 | 39 | .. code-block:: sh 40 | 41 | (venv) $ tox 42 | 43 | By default this will run the test suite multiple times, once on each Python 44 | version supported by Rubicon, as well as running some pre-commit checks of 45 | code style and validity. This can take a while, so if you want to speed up 46 | the process while developing, you can run the tests on one Python version only: 47 | 48 | .. code-block:: sh 49 | 50 | (venv) $ tox -e py 51 | 52 | Or, to run using a specific version of Python: 53 | 54 | .. code-block:: sh 55 | 56 | (venv) $ tox -e py37 57 | 58 | substituting the version number that you want to target. You can also specify 59 | one of the pre-commit checks `flake8`, `docs` or `package` to check code 60 | formatting, documentation syntax and packaging metadata, respectively. 61 | 62 | Now you are ready to start hacking on Rubicon. Have fun! 63 | -------------------------------------------------------------------------------- /docs/how-to/contribute-docs.rst: -------------------------------------------------------------------------------- 1 | Contributing to the documentation 2 | ================================= 3 | 4 | Here are some tips for working on this documentation. You're welcome to add 5 | more and help us out! 6 | 7 | First of all, you should check the `Restructured Text (reST) and Sphinx 8 | CheatSheet `_ to 9 | learn how to write your .rst file. 10 | 11 | Create a .rst file 12 | --------------------- 13 | 14 | Look at the structure and choose the best category to put your .rst file. Make 15 | sure that it is referenced in the index of the corresponding category, so it 16 | will show on in the documentation. If you have no idea how to do this, study 17 | the other index files for clues. 18 | 19 | 20 | Build documentation locally 21 | --------------------------- 22 | 23 | To build the documentation locally, :ref:`set up a development environment 24 | `, and run: 25 | 26 | $ tox -e docs 27 | 28 | The output of the file should be in the ``build/sphinx/html`` folder. If there are 29 | any markup problems, they'll raise an error. 30 | -------------------------------------------------------------------------------- /docs/how-to/get-started.rst: -------------------------------------------------------------------------------- 1 | =============== 2 | Getting Started 3 | =============== 4 | 5 | **TODO** -------------------------------------------------------------------------------- /docs/how-to/index.rst: -------------------------------------------------------------------------------- 1 | .. _how-to: 2 | 3 | ============= 4 | How-to Guides 5 | ============= 6 | 7 | How-to guides are recipes that take the user through steps in key subjects. 8 | They are more advanced than tutorials and assume a lot more about what the user 9 | already knows than tutorials do, and unlike documents in the tutorial they can 10 | stand alone. 11 | 12 | .. toctree:: 13 | :maxdepth: 1 14 | :glob: 15 | 16 | get-started 17 | contribute-code 18 | contribute-docs 19 | -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | .. raw:: html 2 | 3 | 26 | 27 | 28 | ============ 29 | Rubicon Java 30 | ============ 31 | 32 | .. note:: 33 | 34 | This project has been archived. It was used by Briefcase 0.3.9 and earlier 35 | to support Android projects; however, that role is now performed by 36 | `Chaquopy `__. 37 | 38 | Rubicon Java is a bridge between the Java Runtime Environment and Python. It 39 | enables you to: 40 | 41 | * Instantiate objects defined in Java, 42 | * Invoke static and instance methods on objects defined in Java, 43 | * Access and modify static and instance fields on objects defined in Java, and 44 | * Write and use Python implementations of interfaces defined in Java. 45 | 46 | It also includes wrappers of the some key data types from the Java standard 47 | library (e.g., ``java.lang.String``). 48 | 49 | .. rst-class:: row 50 | 51 | Table of contents 52 | ================= 53 | 54 | .. rst-class:: clearfix row 55 | 56 | .. rst-class:: column column2 57 | 58 | :doc:`Tutorial <./tutorial/index>` 59 | ---------------------------------- 60 | 61 | Get started with a hands-on introduction for beginners 62 | 63 | 64 | .. rst-class:: column column2 65 | 66 | :doc:`How-to guides <./how-to/index>` 67 | ------------------------------------- 68 | 69 | Guides and recipes for common problems and tasks, including how to contribute 70 | 71 | 72 | .. rst-class:: column column2 73 | 74 | :doc:`Background <./background/index>` 75 | -------------------------------------- 76 | 77 | Explanation and discussion of key topics and concepts 78 | 79 | 80 | .. rst-class:: column column2 81 | 82 | :doc:`Reference <./reference/index>` 83 | ------------------------------------ 84 | 85 | Technical reference - commands, modules, classes, methods 86 | 87 | 88 | .. rst-class:: clearfix row 89 | 90 | Community 91 | ========= 92 | 93 | Rubicon is part of the `BeeWare suite`_. You can talk to the community through: 94 | 95 | * `@pybeeware on Twitter`_ 96 | 97 | * `beeware/general on Gitter`_ 98 | 99 | .. _BeeWare suite: http://beeware.org 100 | .. _Read The Docs: https://rubicon-java.readthedocs.io 101 | .. _@pybeeware on Twitter: https://twitter.com/pybeeware 102 | .. _beeware/general on Gitter: https://gitter.im/beeware/general 103 | 104 | 105 | .. toctree:: 106 | :maxdepth: 2 107 | :hidden: 108 | :titlesonly: 109 | 110 | tutorial/index 111 | how-to/index 112 | background/index 113 | reference/index 114 | -------------------------------------------------------------------------------- /docs/make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | pushd %~dp0 4 | 5 | REM Command file for Sphinx documentation 6 | 7 | if "%SPHINXBUILD%" == "" ( 8 | set SPHINXBUILD=sphinx-build 9 | ) 10 | set SOURCEDIR=. 11 | set BUILDDIR=_build 12 | 13 | if "%1" == "" goto help 14 | if "%1" == "livehtml" goto livehtml 15 | 16 | %SPHINXBUILD% >NUL 2>NUL 17 | if errorlevel 9009 ( 18 | echo. 19 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx 20 | echo.installed, then set the SPHINXBUILD environment variable to point 21 | echo.to the full path of the 'sphinx-build' executable. Alternatively you 22 | echo.may add the Sphinx directory to PATH. 23 | echo. 24 | echo.If you don't have Sphinx installed, grab it from 25 | echo.http://sphinx-doc.org/ 26 | exit /b 1 27 | ) 28 | 29 | %SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% 30 | goto end 31 | 32 | :help 33 | %SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% 34 | goto end 35 | 36 | :livehtml 37 | sphinx-autobuild -b html %SPHINXOPTS% %SOURCEDIR% %BUILDDIR%/html 38 | goto end 39 | 40 | :end 41 | popd 42 | -------------------------------------------------------------------------------- /docs/reference/index.rst: -------------------------------------------------------------------------------- 1 | ========= 2 | Reference 3 | ========= 4 | 5 | .. toctree:: 6 | :maxdepth: 1 7 | 8 | This is the technical reference for public APIs provided by Rubicon. 9 | -------------------------------------------------------------------------------- /docs/requirements_docs.txt: -------------------------------------------------------------------------------- 1 | sphinx > 4 2 | sphinxcontrib-spelling 3 | pyenchant 4 | sphinx-autobuild 5 | sphinx_rtd_theme 6 | sphinx_tabs -------------------------------------------------------------------------------- /docs/spelling_wordlist: -------------------------------------------------------------------------------- 1 | Alea 2 | BeeWare 3 | callables 4 | Est 5 | Iacta 6 | iOS 7 | macOS 8 | metaclasses 9 | Objective-C 10 | readonly 11 | Roadmap 12 | subclassed 13 | subclassing 14 | 15 | # Usernames 16 | Dayof 17 | jeamland 18 | Longhanks 19 | ojii 20 | stsievert 21 | uranusjr -------------------------------------------------------------------------------- /docs/tutorial/index.rst: -------------------------------------------------------------------------------- 1 | ========= 2 | Tutorials 3 | ========= 4 | 5 | These tutorials are step-by step guides for using Rubicon Java. 6 | 7 | .. toctree:: 8 | :maxdepth: 1 9 | :titlesonly: 10 | 11 | tutorial-1 12 | tutorial-2 13 | 14 | 15 | Tutorial 1 - Your first bridge 16 | ============================== 17 | 18 | In :doc:`tutorial-1`, you will use Rubicon to invoke an existing Java 19 | library on your computer. 20 | 21 | Tutorial 2 - Writing your own class 22 | =================================== 23 | 24 | In :doc:`tutorial-2`, you will write a Python class, and expose it to the 25 | Java runtime. 26 | -------------------------------------------------------------------------------- /docs/tutorial/tutorial-1.rst: -------------------------------------------------------------------------------- 1 | ================= 2 | Your first bridge 3 | ================= 4 | 5 | In this example, we're going to use Rubicon to access the Java standard 6 | library, and the ``java.net.URL`` class in that library. ``java.net.URL`` is 7 | the class used to represent and manipulate URLs. 8 | 9 | This tutorial assumes you've set up your environment as described in the 10 | :doc:`Getting started guide `. 11 | 12 | Accessing ``java.net.URL`` 13 | ========================== 14 | 15 | **TODO** 16 | 17 | Time to take over the world! 18 | ---------------------------- 19 | 20 | You now have access to *any* method, on *any* class, in any library, in the 21 | entire Java ecosystem! If you can invoke something in Java, you 22 | can invoke it in Python - all you need to do is: 23 | 24 | * load the library with ctypes; 25 | * register the classes you want to use; and 26 | * Use those classes as if they were written in Python. 27 | 28 | Next steps 29 | ---------- 30 | 31 | The next step is to write your own classes, and expose them into the Java 32 | runtime. That's the subject of the :doc:`next tutorial <./tutorial-2>`. 33 | -------------------------------------------------------------------------------- /docs/tutorial/tutorial-2.rst: -------------------------------------------------------------------------------- 1 | ====================== 2 | Writing your own class 3 | ====================== 4 | 5 | Eventually, you'll come across an Java API that requires you to provide 6 | a class instance (usually one implementing an interface) as an argument. 7 | For example, when using Java GUI classes, you often need to define "handler" 8 | classes to describe how a GUI element will respond to mouse clicks and key 9 | presses. 10 | 11 | **TODO** 12 | 13 | Next steps 14 | ========== 15 | 16 | ??? 17 | -------------------------------------------------------------------------------- /jni/rubicon.c: -------------------------------------------------------------------------------- 1 | #define __STDC_FORMAT_MACROS 2 | #include 3 | #include 4 | 5 | #include 6 | #ifdef LIBPYTHON_RTLD_GLOBAL 7 | #include 8 | #endif 9 | 10 | #define PY_SSIZE_T_CLEAN 11 | #include "Python.h" 12 | 13 | #include "rubicon.h" 14 | 15 | #ifdef __ANDROID__ 16 | 17 | /************************************************************************** 18 | ************************************************************************** 19 | * Android logging interface 20 | ************************************************************************** 21 | *************************************************************************/ 22 | 23 | #include "android/log.h" 24 | 25 | #define LOG_TAG "Python" 26 | 27 | #define LOG_V(...) __android_log_print(ANDROID_LOG_VERBOSE, LOG_TAG, __VA_ARGS__) 28 | #define LOG_D(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__) 29 | #define LOG_I(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__) 30 | #define LOG_W(...) __android_log_print(ANDROID_LOG_WARN, LOG_TAG, __VA_ARGS__) 31 | #define LOG_E(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__) 32 | 33 | static PyObject *android_verbose(PyObject *self, PyObject *args) { 34 | char *logstr = NULL; 35 | if (!PyArg_ParseTuple(args, "s", &logstr)) { 36 | return NULL; 37 | } 38 | __android_log_write(ANDROID_LOG_VERBOSE, LOG_TAG, logstr); 39 | Py_RETURN_NONE; 40 | } 41 | 42 | static PyObject *android_debug(PyObject *self, PyObject *args) { 43 | char *logstr = NULL; 44 | if (!PyArg_ParseTuple(args, "s", &logstr)) { 45 | return NULL; 46 | } 47 | __android_log_write(ANDROID_LOG_DEBUG, LOG_TAG, logstr); 48 | Py_RETURN_NONE; 49 | } 50 | 51 | static PyObject *android_info(PyObject *self, PyObject *args) { 52 | char *logstr = NULL; 53 | if (!PyArg_ParseTuple(args, "s", &logstr)) { 54 | return NULL; 55 | } 56 | __android_log_write(ANDROID_LOG_INFO, LOG_TAG, logstr); 57 | Py_RETURN_NONE; 58 | } 59 | 60 | static PyObject *android_warn(PyObject *self, PyObject *args) { 61 | char *logstr = NULL; 62 | if (!PyArg_ParseTuple(args, "s", &logstr)) { 63 | return NULL; 64 | } 65 | __android_log_write(ANDROID_LOG_WARN, LOG_TAG, logstr); 66 | Py_RETURN_NONE; 67 | } 68 | 69 | static PyObject *android_error(PyObject *self, PyObject *args) { 70 | char *logstr = NULL; 71 | if (!PyArg_ParseTuple(args, "s", &logstr)) { 72 | return NULL; 73 | } 74 | __android_log_write(ANDROID_LOG_ERROR, LOG_TAG, logstr); 75 | Py_RETURN_NONE; 76 | } 77 | 78 | static PyMethodDef android_methods[] = { 79 | {"verbose", android_verbose, METH_VARARGS, "Write a VERBOSE message to the Android system log."}, 80 | {"debug", android_debug, METH_VARARGS, "Write a DEBUG message to the Android system log."}, 81 | {"info", android_info, METH_VARARGS, "Write an INFO message to the Android system log."}, 82 | {"warn", android_warn, METH_VARARGS, "Write a WARN message to the Android system log."}, 83 | {"error", android_error, METH_VARARGS, "Write an ERROR message to the Android system log."}, 84 | {NULL, NULL, 0, NULL}}; 85 | 86 | static struct PyModuleDef android_definition = { 87 | PyModuleDef_HEAD_INIT, 88 | "android", 89 | "Android logging wrappers", 90 | -1, 91 | android_methods, 92 | NULL, 93 | NULL, 94 | NULL, 95 | NULL, 96 | }; 97 | 98 | PyMODINIT_FUNC 99 | PyInit_android(void) 100 | { 101 | return PyModule_Create(&android_definition); 102 | } 103 | 104 | #else 105 | 106 | #define LOG_V(...) printf(""); 107 | #define LOG_D(...) printf(""); 108 | // #define LOG_V(...) printf(__VA_ARGS__); printf("\n") 109 | // #define LOG_D(...) printf(__VA_ARGS__); printf("\n") 110 | #define LOG_I(...) \ 111 | printf(__VA_ARGS__); \ 112 | printf("\n") 113 | #define LOG_W(...) \ 114 | printf(__VA_ARGS__); \ 115 | printf("\n") 116 | #define LOG_E(...) \ 117 | printf(__VA_ARGS__); \ 118 | printf("\n") 119 | 120 | #endif 121 | 122 | /************************************************************************** 123 | ************************************************************************** 124 | * Python JNI interface 125 | ************************************************************************** 126 | *************************************************************************/ 127 | 128 | // The JNIEnv associated with the Python runtime 129 | JNIEnv *java = NULL; 130 | 131 | // The Python method dispatch handler 132 | static PyObject *method_handler = NULL; 133 | 134 | /************************************************************************** 135 | * Wrappers around JNI methods, bound to the JNIEnv associated with the 136 | * Python runtime. 137 | * 138 | * These methods should not be invoked until the Python runtime 139 | * has been started. 140 | *************************************************************************/ 141 | jint GetVersion() { 142 | return (*java)->GetVersion(java); 143 | } 144 | jclass DefineClass(const char *name, jobject loader, const jbyte *buf, jsize len) { 145 | return (*java)->DefineClass(java, name, loader, buf, len); 146 | } 147 | jclass FindClass(const char *name) { 148 | return (*java)->FindClass(java, name); 149 | } 150 | jmethodID FromReflectedMethod(jobject method) { 151 | return (*java)->FromReflectedMethod(java, method); 152 | } 153 | jfieldID FromReflectedField(jobject field) { 154 | return (*java)->FromReflectedField(java, field); 155 | } 156 | 157 | jobject ToReflectedMethod(jclass cls, jmethodID methodID, jboolean isStatic) { 158 | return (*java)->ToReflectedMethod(java, cls, methodID, isStatic); 159 | } 160 | 161 | jclass GetSuperclass(jclass sub) { 162 | return (*java)->GetSuperclass(java, sub); 163 | } 164 | jboolean IsAssignableFrom(jclass sub, jclass sup) { 165 | return (*java)->IsAssignableFrom(java, sub, sup); 166 | } 167 | 168 | jobject ToReflectedField(jclass cls, jfieldID fieldID, jboolean isStatic) { 169 | return (*java)->ToReflectedField(java, cls, fieldID, isStatic); 170 | } 171 | 172 | jint Throw(jthrowable obj) { 173 | return (*java)->Throw(java, obj); 174 | } 175 | jint ThrowNew(jclass cls, const char *msg) { 176 | return (*java)->ThrowNew(java, cls, msg); 177 | } 178 | jthrowable ExceptionOccurred() { 179 | return (*java)->ExceptionOccurred(java); 180 | } 181 | void ExceptionDescribe() { 182 | (*java)->ExceptionDescribe(java); 183 | } 184 | void ExceptionClear() { 185 | (*java)->ExceptionClear(java); 186 | } 187 | void FatalError(const char *msg) { 188 | (*java)->FatalError(java, msg); 189 | } 190 | 191 | jint PushLocalFrame(jint capacity) { 192 | return (*java)->PushLocalFrame(java, capacity); 193 | } 194 | jobject PopLocalFrame(jobject result) { 195 | return (*java)->PopLocalFrame(java, result); 196 | } 197 | 198 | jobject NewGlobalRef(jobject lobj) { 199 | return (*java)->NewGlobalRef(java, lobj); 200 | } 201 | void DeleteGlobalRef(jobject gref) { 202 | (*java)->DeleteGlobalRef(java, gref); 203 | } 204 | void DeleteLocalRef(jobject obj) { 205 | (*java)->DeleteLocalRef(java, obj); 206 | } 207 | 208 | jboolean IsSameObject(jobject obj1, jobject obj2) { 209 | return (*java)->IsSameObject(java, obj1, obj2); 210 | } 211 | 212 | jobject NewLocalRef(jobject ref) { 213 | return (*java)->NewLocalRef(java, ref); 214 | } 215 | jint EnsureLocalCapacity(jint capacity) { 216 | return (*java)->EnsureLocalCapacity(java, capacity); 217 | } 218 | 219 | jobject AllocObject(jclass cls) { 220 | return (*java)->AllocObject(java, cls); 221 | } 222 | jobject NewObject(jclass cls, jmethodID methodID, ...) { 223 | va_list args; 224 | jobject result; 225 | va_start(args, methodID); 226 | result = (*java)->NewObjectV(java, cls, methodID, args); 227 | va_end(args); 228 | return result; 229 | } 230 | 231 | jclass GetObjectClass(jobject obj) { 232 | return (*java)->GetObjectClass(java, obj); 233 | } 234 | jboolean IsInstanceOf(jobject obj, jclass cls) { 235 | return (*java)->IsInstanceOf(java, obj, cls); 236 | } 237 | 238 | jmethodID GetMethodID(jclass cls, const char *name, const char *sig) { 239 | return (*java)->GetMethodID(java, cls, name, sig); 240 | } 241 | 242 | jobject CallObjectMethod(jobject obj, jmethodID methodID, ...) { 243 | va_list args; 244 | jobject result; 245 | va_start(args, methodID); 246 | result = (*java)->CallObjectMethodV(java, obj, methodID, args); 247 | va_end(args); 248 | return result; 249 | } 250 | jboolean CallBooleanMethod(jobject obj, jmethodID methodID, ...) { 251 | va_list args; 252 | jboolean result; 253 | va_start(args, methodID); 254 | result = (*java)->CallBooleanMethodV(java, obj, methodID, args); 255 | va_end(args); 256 | return result; 257 | } 258 | jbyte CallByteMethod(jobject obj, jmethodID methodID, ...) { 259 | va_list args; 260 | jbyte result; 261 | va_start(args, methodID); 262 | result = (*java)->CallByteMethodV(java, obj, methodID, args); 263 | va_end(args); 264 | return result; 265 | } 266 | jchar CallCharMethod(jobject obj, jmethodID methodID, ...) { 267 | va_list args; 268 | jchar result; 269 | va_start(args, methodID); 270 | result = (*java)->CallCharMethodV(java, obj, methodID, args); 271 | va_end(args); 272 | return result; 273 | } 274 | jshort CallShortMethod(jobject obj, jmethodID methodID, ...) { 275 | va_list args; 276 | jshort result; 277 | va_start(args, methodID); 278 | result = (*java)->CallShortMethodV(java, obj, methodID, args); 279 | va_end(args); 280 | return result; 281 | } 282 | jint CallIntMethod(jobject obj, jmethodID methodID, ...) { 283 | va_list args; 284 | jint result; 285 | va_start(args, methodID); 286 | result = (*java)->CallIntMethodV(java, obj, methodID, args); 287 | va_end(args); 288 | return result; 289 | } 290 | jlong CallLongMethod(jobject obj, jmethodID methodID, ...) { 291 | va_list args; 292 | jlong result; 293 | va_start(args, methodID); 294 | result = (*java)->CallLongMethodV(java, obj, methodID, args); 295 | va_end(args); 296 | return result; 297 | } 298 | jfloat CallFloatMethod(jobject obj, jmethodID methodID, ...) { 299 | va_list args; 300 | jfloat result; 301 | va_start(args, methodID); 302 | result = (*java)->CallFloatMethodV(java, obj, methodID, args); 303 | va_end(args); 304 | return result; 305 | } 306 | jdouble CallDoubleMethod(jobject obj, jmethodID methodID, ...) { 307 | va_list args; 308 | jdouble result; 309 | va_start(args, methodID); 310 | result = (*java)->CallDoubleMethodV(java, obj, methodID, args); 311 | va_end(args); 312 | return result; 313 | } 314 | void CallVoidMethod(jobject obj, jmethodID methodID, ...) { 315 | va_list args; 316 | va_start(args, methodID); 317 | (*java)->CallVoidMethodV(java, obj, methodID, args); 318 | va_end(args); 319 | } 320 | 321 | jobject CallNonvirtualObjectMethod(jobject obj, jclass cls, jmethodID methodID, ...) { 322 | va_list args; 323 | jobject result; 324 | va_start(args, methodID); 325 | result = (*java)->CallNonvirtualObjectMethodV(java, obj, cls, methodID, args); 326 | va_end(args); 327 | return result; 328 | } 329 | jboolean CallNonvirtualBooleanMethod(jobject obj, jclass cls, jmethodID methodID, ...) { 330 | va_list args; 331 | jboolean result; 332 | va_start(args, methodID); 333 | result = (*java)->CallNonvirtualBooleanMethodV(java, obj, cls, methodID, args); 334 | va_end(args); 335 | return result; 336 | } 337 | jbyte CallNonvirtualByteMethod(jobject obj, jclass cls, jmethodID methodID, ...) { 338 | va_list args; 339 | jbyte result; 340 | va_start(args, methodID); 341 | result = (*java)->CallNonvirtualByteMethodV(java, obj, cls, methodID, args); 342 | va_end(args); 343 | return result; 344 | } 345 | jchar CallNonvirtualCharMethod(jobject obj, jclass cls, jmethodID methodID, ...) { 346 | va_list args; 347 | jchar result; 348 | va_start(args, methodID); 349 | result = (*java)->CallNonvirtualCharMethodV(java, obj, cls, methodID, args); 350 | va_end(args); 351 | return result; 352 | } 353 | jshort CallNonvirtualShortMethod(jobject obj, jclass cls, jmethodID methodID, ...) { 354 | va_list args; 355 | jshort result; 356 | va_start(args, methodID); 357 | result = (*java)->CallNonvirtualShortMethodV(java, obj, cls, methodID, args); 358 | va_end(args); 359 | return result; 360 | } 361 | jint CallNonvirtualIntMethod(jobject obj, jclass cls, jmethodID methodID, ...) { 362 | va_list args; 363 | jint result; 364 | va_start(args, methodID); 365 | result = (*java)->CallNonvirtualIntMethodV(java, obj, cls, methodID, args); 366 | va_end(args); 367 | return result; 368 | } 369 | jlong CallNonvirtualLongMethod(jobject obj, jclass cls, jmethodID methodID, ...) { 370 | va_list args; 371 | jlong result; 372 | va_start(args, methodID); 373 | result = (*java)->CallNonvirtualLongMethodV(java, obj, cls, methodID, args); 374 | va_end(args); 375 | return result; 376 | } 377 | jfloat CallNonvirtualFloatMethod(jobject obj, jclass cls, jmethodID methodID, ...) { 378 | va_list args; 379 | jfloat result; 380 | va_start(args, methodID); 381 | result = (*java)->CallNonvirtualFloatMethodV(java, obj, cls, methodID, args); 382 | va_end(args); 383 | return result; 384 | } 385 | jdouble CallNonvirtualDoubleMethod(jobject obj, jclass cls, jmethodID methodID, ...) { 386 | va_list args; 387 | jdouble result; 388 | va_start(args, methodID); 389 | result = (*java)->CallNonvirtualDoubleMethodV(java, obj, cls, methodID, args); 390 | va_end(args); 391 | return result; 392 | } 393 | void CallNonvirtualVoidMethod(jobject obj, jclass cls, jmethodID methodID, ...) { 394 | va_list args; 395 | va_start(args, methodID); 396 | (*java)->CallNonvirtualVoidMethodV(java, obj, cls, methodID, args); 397 | va_end(args); 398 | } 399 | 400 | jfieldID GetFieldID(jclass cls, const char *name, const char *sig) { 401 | return (*java)->GetFieldID(java, cls, name, sig); 402 | } 403 | 404 | jobject GetObjectField(jobject obj, jfieldID fieldID) { 405 | return (*java)->GetObjectField(java, obj, fieldID); 406 | } 407 | jboolean GetBooleanField(jobject obj, jfieldID fieldID) { 408 | return (*java)->GetBooleanField(java, obj, fieldID); 409 | } 410 | jbyte GetByteField(jobject obj, jfieldID fieldID) { 411 | return (*java)->GetByteField(java, obj, fieldID); 412 | } 413 | jchar GetCharField(jobject obj, jfieldID fieldID) { 414 | return (*java)->GetCharField(java, obj, fieldID); 415 | } 416 | jshort GetShortField(jobject obj, jfieldID fieldID) { 417 | return (*java)->GetShortField(java, obj, fieldID); 418 | } 419 | jint GetIntField(jobject obj, jfieldID fieldID) { 420 | return (*java)->GetIntField(java, obj, fieldID); 421 | } 422 | jlong GetLongField(jobject obj, jfieldID fieldID) { 423 | return (*java)->GetLongField(java, obj, fieldID); 424 | } 425 | jfloat GetFloatField(jobject obj, jfieldID fieldID) { 426 | return (*java)->GetFloatField(java, obj, fieldID); 427 | } 428 | jdouble GetDoubleField(jobject obj, jfieldID fieldID) { 429 | return (*java)->GetDoubleField(java, obj, fieldID); 430 | } 431 | 432 | void SetObjectField(jobject obj, jfieldID fieldID, jobject val) { 433 | (*java)->SetObjectField(java, obj, fieldID, val); 434 | } 435 | void SetBooleanField(jobject obj, jfieldID fieldID, jboolean val) { 436 | (*java)->SetBooleanField(java, obj, fieldID, val); 437 | } 438 | void SetByteField(jobject obj, jfieldID fieldID, jbyte val) { 439 | (*java)->SetByteField(java, obj, fieldID, val); 440 | } 441 | void SetCharField(jobject obj, jfieldID fieldID, jchar val) { 442 | (*java)->SetCharField(java, obj, fieldID, val); 443 | } 444 | void SetShortField(jobject obj, jfieldID fieldID, jshort val) { 445 | (*java)->SetShortField(java, obj, fieldID, val); 446 | } 447 | void SetIntField(jobject obj, jfieldID fieldID, jint val) { 448 | (*java)->SetIntField(java, obj, fieldID, val); 449 | } 450 | void SetLongField(jobject obj, jfieldID fieldID, jlong val) { 451 | (*java)->SetLongField(java, obj, fieldID, val); 452 | } 453 | void SetFloatField(jobject obj, jfieldID fieldID, jfloat val) { 454 | (*java)->SetFloatField(java, obj, fieldID, val); 455 | } 456 | void SetDoubleField(jobject obj, jfieldID fieldID, jdouble val) { 457 | (*java)->SetDoubleField(java, obj, fieldID, val); 458 | } 459 | 460 | jmethodID GetStaticMethodID(jclass cls, const char *name, const char *sig) { 461 | return (*java)->GetStaticMethodID(java, cls, name, sig); 462 | } 463 | 464 | jobject CallStaticObjectMethod(jclass cls, jmethodID methodID, ...) { 465 | va_list args; 466 | jobject result; 467 | va_start(args, methodID); 468 | result = (*java)->CallStaticObjectMethodV(java, cls, methodID, args); 469 | va_end(args); 470 | return result; 471 | } 472 | jboolean CallStaticBooleanMethod(jclass cls, jmethodID methodID, ...) { 473 | va_list args; 474 | jboolean result; 475 | va_start(args, methodID); 476 | result = (*java)->CallStaticBooleanMethodV(java, cls, methodID, args); 477 | va_end(args); 478 | return result; 479 | } 480 | jbyte CallStaticByteMethod(jclass cls, jmethodID methodID, ...) { 481 | va_list args; 482 | jbyte result; 483 | va_start(args, methodID); 484 | result = (*java)->CallStaticByteMethodV(java, cls, methodID, args); 485 | va_end(args); 486 | return result; 487 | } 488 | jchar CallStaticCharMethod(jclass cls, jmethodID methodID, ...) { 489 | va_list args; 490 | jchar result; 491 | va_start(args, methodID); 492 | result = (*java)->CallStaticCharMethodV(java, cls, methodID, args); 493 | va_end(args); 494 | return result; 495 | } 496 | jshort CallStaticShortMethod(jclass cls, jmethodID methodID, ...) { 497 | va_list args; 498 | jshort result; 499 | va_start(args, methodID); 500 | result = (*java)->CallStaticShortMethodV(java, cls, methodID, args); 501 | va_end(args); 502 | return result; 503 | } 504 | jint CallStaticIntMethod(jclass cls, jmethodID methodID, ...) { 505 | va_list args; 506 | jint result; 507 | va_start(args, methodID); 508 | result = (*java)->CallStaticIntMethodV(java, cls, methodID, args); 509 | va_end(args); 510 | return result; 511 | } 512 | jlong CallStaticLongMethod(jclass cls, jmethodID methodID, ...) { 513 | va_list args; 514 | jlong result; 515 | va_start(args, methodID); 516 | result = (*java)->CallStaticLongMethodV(java, cls, methodID, args); 517 | va_end(args); 518 | return result; 519 | } 520 | jfloat CallStaticFloatMethod(jclass cls, jmethodID methodID, ...) { 521 | va_list args; 522 | jfloat result; 523 | va_start(args, methodID); 524 | result = (*java)->CallStaticFloatMethodV(java, cls, methodID, args); 525 | va_end(args); 526 | return result; 527 | } 528 | jdouble CallStaticDoubleMethod(jclass cls, jmethodID methodID, ...) { 529 | va_list args; 530 | jdouble result; 531 | va_start(args, methodID); 532 | result = (*java)->CallStaticDoubleMethodV(java, cls, methodID, args); 533 | va_end(args); 534 | return result; 535 | } 536 | void CallStaticVoidMethod(jclass cls, jmethodID methodID, ...) { 537 | va_list args; 538 | va_start(args, methodID); 539 | (*java)->CallStaticVoidMethodV(java, cls, methodID, args); 540 | va_end(args); 541 | } 542 | 543 | jfieldID GetStaticFieldID(jclass cls, const char *name, const char *sig) { 544 | return (*java)->GetStaticFieldID(java, cls, name, sig); 545 | } 546 | jobject GetStaticObjectField(jclass cls, jfieldID fieldID) { 547 | return (*java)->GetStaticObjectField(java, cls, fieldID); 548 | } 549 | jboolean GetStaticBooleanField(jclass cls, jfieldID fieldID) { 550 | return (*java)->GetStaticBooleanField(java, cls, fieldID); 551 | } 552 | jbyte GetStaticByteField(jclass cls, jfieldID fieldID) { 553 | return (*java)->GetStaticByteField(java, cls, fieldID); 554 | } 555 | jchar GetStaticCharField(jclass cls, jfieldID fieldID) { 556 | return (*java)->GetStaticCharField(java, cls, fieldID); 557 | } 558 | jshort GetStaticShortField(jclass cls, jfieldID fieldID) { 559 | return (*java)->GetStaticShortField(java, cls, fieldID); 560 | } 561 | jint GetStaticIntField(jclass cls, jfieldID fieldID) { 562 | return (*java)->GetStaticIntField(java, cls, fieldID); 563 | } 564 | jlong GetStaticLongField(jclass cls, jfieldID fieldID) { 565 | return (*java)->GetStaticLongField(java, cls, fieldID); 566 | } 567 | jfloat GetStaticFloatField(jclass cls, jfieldID fieldID) { 568 | return (*java)->GetStaticFloatField(java, cls, fieldID); 569 | } 570 | jdouble GetStaticDoubleField(jclass cls, jfieldID fieldID) { 571 | return (*java)->GetStaticDoubleField(java, cls, fieldID); 572 | } 573 | 574 | void SetStaticObjectField(jclass cls, jfieldID fieldID, jobject value) { 575 | (*java)->SetStaticObjectField(java, cls, fieldID, value); 576 | } 577 | void SetStaticBooleanField(jclass cls, jfieldID fieldID, jboolean value) { 578 | (*java)->SetStaticBooleanField(java, cls, fieldID, value); 579 | } 580 | void SetStaticByteField(jclass cls, jfieldID fieldID, jbyte value) { 581 | (*java)->SetStaticByteField(java, cls, fieldID, value); 582 | } 583 | void SetStaticCharField(jclass cls, jfieldID fieldID, jchar value) { 584 | (*java)->SetStaticCharField(java, cls, fieldID, value); 585 | } 586 | void SetStaticShortField(jclass cls, jfieldID fieldID, jshort value) { 587 | (*java)->SetStaticShortField(java, cls, fieldID, value); 588 | } 589 | void SetStaticIntField(jclass cls, jfieldID fieldID, jint value) { 590 | (*java)->SetStaticIntField(java, cls, fieldID, value); 591 | } 592 | void SetStaticLongField(jclass cls, jfieldID fieldID, jlong value) { 593 | (*java)->SetStaticLongField(java, cls, fieldID, value); 594 | } 595 | void SetStaticFloatField(jclass cls, jfieldID fieldID, jfloat value) { 596 | (*java)->SetStaticFloatField(java, cls, fieldID, value); 597 | } 598 | void SetStaticDoubleField(jclass cls, jfieldID fieldID, jdouble value) { 599 | (*java)->SetStaticDoubleField(java, cls, fieldID, value); 600 | } 601 | 602 | jstring NewString(const jchar *unicode, jsize len) { 603 | return (*java)->NewString(java, unicode, len); 604 | } 605 | jsize GetStringLength(jstring str) { 606 | return (*java)->GetStringLength(java, str); 607 | } 608 | const jchar *GetStringChars(jstring str, jboolean *isCopy) { 609 | return (*java)->GetStringChars(java, str, isCopy); 610 | } 611 | void ReleaseStringChars(jstring str, const jchar *chars) { 612 | (*java)->ReleaseStringChars(java, str, chars); 613 | } 614 | 615 | jstring NewStringUTF(const char *utf) { 616 | return (*java)->NewStringUTF(java, utf); 617 | } 618 | jsize GetStringUTFLength(jstring str) { 619 | return (*java)->GetStringUTFLength(java, str); 620 | } 621 | const char *GetStringUTFChars(jstring str, jboolean *isCopy) { 622 | return (*java)->GetStringUTFChars(java, str, isCopy); 623 | } 624 | void ReleaseStringUTFChars(jstring str, const char *chars) { 625 | (*java)->ReleaseStringUTFChars(java, str, chars); 626 | } 627 | 628 | jsize GetArrayLength(jarray array) { 629 | return (*java)->GetArrayLength(java, array); 630 | } 631 | 632 | jobjectArray NewObjectArray(jsize len, jclass cls, jobject init) { 633 | return (*java)->NewObjectArray(java, len, cls, init); 634 | } 635 | jobject GetObjectArrayElement(jobjectArray array, jsize index) { 636 | return (*java)->GetObjectArrayElement(java, array, index); 637 | } 638 | void SetObjectArrayElement(jobjectArray array, jsize index, jobject val) { 639 | (*java)->SetObjectArrayElement(java, array, index, val); 640 | } 641 | 642 | jbooleanArray NewBooleanArray(jsize len) { 643 | return (*java)->NewBooleanArray(java, len); 644 | } 645 | jbyteArray NewByteArray(jsize len) { 646 | return (*java)->NewByteArray(java, len); 647 | } 648 | jcharArray NewCharArray(jsize len) { 649 | return (*java)->NewCharArray(java, len); 650 | } 651 | jshortArray NewShortArray(jsize len) { 652 | return (*java)->NewShortArray(java, len); 653 | } 654 | jintArray NewIntArray(jsize len) { 655 | return (*java)->NewIntArray(java, len); 656 | } 657 | jlongArray NewLongArray(jsize len) { 658 | return (*java)->NewLongArray(java, len); 659 | } 660 | jfloatArray NewFloatArray(jsize len) { 661 | return (*java)->NewFloatArray(java, len); 662 | } 663 | jdoubleArray NewDoubleArray(jsize len) { 664 | return (*java)->NewDoubleArray(java, len); 665 | } 666 | 667 | jboolean *GetBooleanArrayElements(jbooleanArray array, jboolean *isCopy) { 668 | return (*java)->GetBooleanArrayElements(java, array, isCopy); 669 | } 670 | jbyte *GetByteArrayElements(jbyteArray array, jboolean *isCopy) { 671 | return (*java)->GetByteArrayElements(java, array, isCopy); 672 | } 673 | jchar *GetCharArrayElements(jcharArray array, jboolean *isCopy) { 674 | return (*java)->GetCharArrayElements(java, array, isCopy); 675 | } 676 | jshort *GetShortArrayElements(jshortArray array, jboolean *isCopy) { 677 | return (*java)->GetShortArrayElements(java, array, isCopy); 678 | } 679 | jint *GetIntArrayElements(jintArray array, jboolean *isCopy) { 680 | return (*java)->GetIntArrayElements(java, array, isCopy); 681 | } 682 | jlong *GetLongArrayElements(jlongArray array, jboolean *isCopy) { 683 | return (*java)->GetLongArrayElements(java, array, isCopy); 684 | } 685 | jfloat *GetFloatArrayElements(jfloatArray array, jboolean *isCopy) { 686 | return (*java)->GetFloatArrayElements(java, array, isCopy); 687 | } 688 | jdouble *GetDoubleArrayElements(jdoubleArray array, jboolean *isCopy) { 689 | return (*java)->GetDoubleArrayElements(java, array, isCopy); 690 | } 691 | 692 | void ReleaseBooleanArrayElements(jbooleanArray array, jboolean *elems, jint mode) { 693 | (*java)->ReleaseBooleanArrayElements(java, array, elems, mode); 694 | } 695 | void ReleaseByteArrayElements(jbyteArray array, jbyte *elems, jint mode) { 696 | (*java)->ReleaseByteArrayElements(java, array, elems, mode); 697 | } 698 | void ReleaseCharArrayElements(jcharArray array, jchar *elems, jint mode) { 699 | (*java)->ReleaseCharArrayElements(java, array, elems, mode); 700 | } 701 | void ReleaseShortArrayElements(jshortArray array, jshort *elems, jint mode) { 702 | (*java)->ReleaseShortArrayElements(java, array, elems, mode); 703 | } 704 | void ReleaseIntArrayElements(jintArray array, jint *elems, jint mode) { 705 | (*java)->ReleaseIntArrayElements(java, array, elems, mode); 706 | } 707 | void ReleaseLongArrayElements(jlongArray array, jlong *elems, jint mode) { 708 | (*java)->ReleaseLongArrayElements(java, array, elems, mode); 709 | } 710 | void ReleaseFloatArrayElements(jfloatArray array, jfloat *elems, jint mode) { 711 | (*java)->ReleaseFloatArrayElements(java, array, elems, mode); 712 | } 713 | void ReleaseDoubleArrayElements(jdoubleArray array, jdouble *elems, jint mode) { 714 | (*java)->ReleaseDoubleArrayElements(java, array, elems, mode); 715 | } 716 | 717 | void GetBooleanArrayRegion(jbooleanArray array, jsize start, jsize len, jboolean *buf) { 718 | (*java)->GetBooleanArrayRegion(java, array, start, len, buf); 719 | } 720 | void GetByteArrayRegion(jbyteArray array, jsize start, jsize len, jbyte *buf) { 721 | (*java)->GetByteArrayRegion(java, array, start, len, buf); 722 | } 723 | void GetCharArrayRegion(jcharArray array, jsize start, jsize len, jchar *buf) { 724 | (*java)->GetCharArrayRegion(java, array, start, len, buf); 725 | } 726 | void GetShortArrayRegion(jshortArray array, jsize start, jsize len, jshort *buf) { 727 | (*java)->GetShortArrayRegion(java, array, start, len, buf); 728 | } 729 | void GetIntArrayRegion(jintArray array, jsize start, jsize len, jint *buf) { 730 | (*java)->GetIntArrayRegion(java, array, start, len, buf); 731 | } 732 | void GetLongArrayRegion(jlongArray array, jsize start, jsize len, jlong *buf) { 733 | (*java)->GetLongArrayRegion(java, array, start, len, buf); 734 | } 735 | void GetFloatArrayRegion(jfloatArray array, jsize start, jsize len, jfloat *buf) { 736 | (*java)->GetFloatArrayRegion(java, array, start, len, buf); 737 | } 738 | void GetDoubleArrayRegion(jdoubleArray array, jsize start, jsize len, jdouble *buf) { 739 | (*java)->GetDoubleArrayRegion(java, array, start, len, buf); 740 | } 741 | 742 | void SetBooleanArrayRegion(jbooleanArray array, jsize start, jsize len, const jboolean *buf) { 743 | (*java)->SetBooleanArrayRegion(java, array, start, len, buf); 744 | } 745 | void SetByteArrayRegion(jbyteArray array, jsize start, jsize len, const jbyte *buf) { 746 | (*java)->SetByteArrayRegion(java, array, start, len, buf); 747 | } 748 | void SetCharArrayRegion(jcharArray array, jsize start, jsize len, const jchar *buf) { 749 | (*java)->SetCharArrayRegion(java, array, start, len, buf); 750 | } 751 | void SetShortArrayRegion(jshortArray array, jsize start, jsize len, const jshort *buf) { 752 | (*java)->SetShortArrayRegion(java, array, start, len, buf); 753 | } 754 | void SetIntArrayRegion(jintArray array, jsize start, jsize len, const jint *buf) { 755 | (*java)->SetIntArrayRegion(java, array, start, len, buf); 756 | } 757 | void SetLongArrayRegion(jlongArray array, jsize start, jsize len, const jlong *buf) { 758 | (*java)->SetLongArrayRegion(java, array, start, len, buf); 759 | } 760 | void SetFloatArrayRegion(jfloatArray array, jsize start, jsize len, const jfloat *buf) { 761 | (*java)->SetFloatArrayRegion(java, array, start, len, buf); 762 | } 763 | void SetDoubleArrayRegion(jdoubleArray array, jsize start, jsize len, const jdouble *buf) { 764 | (*java)->SetDoubleArrayRegion(java, array, start, len, buf); 765 | } 766 | 767 | jint RegisterNatives(jclass cls, const JNINativeMethod *methods, jint nMethods) { 768 | return (*java)->RegisterNatives(java, cls, methods, nMethods); 769 | } 770 | jint UnregisterNatives(jclass cls) { 771 | return (*java)->UnregisterNatives(java, cls); 772 | } 773 | 774 | jint MonitorEnter(jobject obj) { 775 | return (*java)->MonitorEnter(java, obj); 776 | } 777 | jint MonitorExit(jobject obj) { 778 | return (*java)->MonitorExit(java, obj); 779 | } 780 | 781 | jint GetJavaVM(JavaVM **vm) { 782 | return (*java)->GetJavaVM(java, vm); 783 | } 784 | 785 | void GetStringRegion(jstring str, jsize start, jsize len, jchar *buf) { 786 | (*java)->GetStringRegion(java, str, start, len, buf); 787 | } 788 | void GetStringUTFRegion(jstring str, jsize start, jsize len, char *buf) { 789 | (*java)->GetStringUTFRegion(java, str, start, len, buf); 790 | } 791 | 792 | void *GetPrimitiveArrayCritical(jarray array, jboolean *isCopy) { 793 | return (*java)->GetPrimitiveArrayCritical(java, array, isCopy); 794 | } 795 | void ReleasePrimitiveArrayCritical(jarray array, void *carray, jint mode) { 796 | (*java)->ReleasePrimitiveArrayCritical(java, array, carray, mode); 797 | } 798 | 799 | const jchar *GetStringCritical(jstring string, jboolean *isCopy) { 800 | return (*java)->GetStringCritical(java, string, isCopy); 801 | } 802 | void ReleaseStringCritical(jstring string, const jchar *cstring) { 803 | (*java)->ReleaseStringCritical(java, string, cstring); 804 | } 805 | 806 | jweak NewWeakGlobalRef(jobject obj) { 807 | return (*java)->NewWeakGlobalRef(java, obj); 808 | } 809 | void DeleteWeakGlobalRef(jweak ref) { 810 | (*java)->DeleteWeakGlobalRef(java, ref); 811 | } 812 | 813 | jboolean ExceptionCheck() { 814 | return (*java)->ExceptionCheck(java); 815 | } 816 | 817 | jobject NewDirectByteBuffer(void *address, jlong capacity) { 818 | return (*java)->NewDirectByteBuffer(java, address, capacity); 819 | } 820 | void *GetDirectBufferAddress(jobject buf) { 821 | return (*java)->GetDirectBufferAddress(java, buf); 822 | } 823 | jlong GetDirectBufferCapacity(jobject buf) { 824 | return (*java)->GetDirectBufferCapacity(java, buf); 825 | } 826 | jobjectRefType GetObjectRefType(jobject obj) { 827 | return (*java)->GetObjectRefType(java, obj); 828 | } 829 | 830 | /************************************************************************** 831 | * Method to start the Python runtime. 832 | *************************************************************************/ 833 | JNIEXPORT jint JNICALL Java_org_beeware_rubicon_Python_init(JNIEnv *env, jobject thisObj, jstring pythonHome, jstring pythonPath, jstring rubiconLib) { 834 | int ret = 0; 835 | char pythonPathVar[512]; 836 | 837 | LOG_I("Start Python runtime..."); 838 | java = env; 839 | 840 | #ifdef LIBPYTHON_RTLD_GLOBAL 841 | // make libpython symbols availiable for everyone 842 | dlopen(LIBPYTHON_RTLD_GLOBAL, RTLD_LAZY | RTLD_GLOBAL); 843 | #endif 844 | 845 | if (pythonHome) { 846 | LOG_D("PYTHONHOME=%s", (*env)->GetStringUTFChars(env, pythonHome, NULL)); 847 | const char *python_home; 848 | wchar_t *wpython_home; 849 | python_home = (*env)->GetStringUTFChars(env, pythonHome, NULL); 850 | wpython_home = Py_DecodeLocale(python_home, NULL); 851 | Py_SetPythonHome(wpython_home); 852 | } else { 853 | LOG_D("Using default PYTHONHOME"); 854 | } 855 | 856 | if (pythonPath) { 857 | sprintf(pythonPathVar, "PYTHONPATH=%s", (*env)->GetStringUTFChars(env, pythonPath, NULL)); 858 | LOG_D("%s", pythonPathVar); 859 | putenv(pythonPathVar); 860 | } else { 861 | LOG_D("Using default PYTHONPATH"); 862 | } 863 | 864 | #ifdef __ANDROID__ 865 | // If we're on android, we need to specify the location of the Rubicon 866 | // shared library as part of the environment. 867 | char rubiconLibVar[256]; 868 | if (rubiconLib) { 869 | sprintf(rubiconLibVar, "RUBICON_LIBRARY=%s", (*env)->GetStringUTFChars(env, rubiconLib, NULL)); 870 | LOG_D("%s", rubiconLibVar); 871 | putenv(rubiconLibVar); 872 | } else { 873 | LOG_D("Not setting RUBICON_LIBRARY"); 874 | } 875 | 876 | // Initialize and bootstrap the Android logging module 877 | LOG_I("Adding Android logging module to default modules..."); 878 | int append_inittab_result = PyImport_AppendInittab("android", PyInit_android); 879 | if (append_inittab_result == -1) { 880 | LOG_E("Error: could not append Android logging module to default modules"); 881 | } 882 | #endif 883 | 884 | // putenv("PYTHONVERBOSE=1"); 885 | 886 | LOG_D("Initializing Python runtime..."); 887 | Py_Initialize(); 888 | 889 | #if PY_VERSION_HEX < 0x03070000 890 | LOG_D("Initializing Python threads..."); 891 | // If other modules are using threads, we need to initialize them before. 892 | PyEval_InitThreads(); 893 | #endif 894 | 895 | #ifdef __ANDROID__ 896 | LOG_D("Replacing sys.stdout/sys.stderr with Android log wrappers..."); 897 | ret = PyRun_SimpleString( 898 | "import sys\n" 899 | "import android\n" 900 | "class LogFile:\n" 901 | " def __init__(self, level):\n" 902 | " self.buffer = ''\n" 903 | " self.level = level\n" 904 | " def write(self, s):\n" 905 | " s = self.buffer + s\n" 906 | " lines = s.split(\"\\n\")\n" 907 | " for line in lines[:-1]:\n" 908 | " self.level(line)\n" 909 | " self.buffer = lines[-1]\n" 910 | " def flush(self):\n" 911 | " return\n" 912 | "sys.stdout = LogFile(android.info)\n" 913 | "sys.stderr = LogFile(android.error)\n" 914 | "print('sys.stdout/stderr replaced with Android log wrappers.')"); 915 | if (ret != 0) { 916 | LOG_E("Exception while routing sys.stdout/stderr to Android log."); 917 | } 918 | #endif 919 | 920 | LOG_V("Import rubicon..."); 921 | PyObject *rubicon; 922 | 923 | rubicon = PyImport_ImportModule("rubicon.java"); 924 | if (rubicon == NULL) { 925 | LOG_E("Couldn't import rubicon python module"); 926 | PyErr_Print(); 927 | PyErr_Clear(); 928 | java = NULL; 929 | return -1; 930 | } 931 | LOG_V("Got rubicon python module"); 932 | 933 | method_handler = PyObject_GetAttrString(rubicon, "dispatch"); 934 | if (method_handler == NULL) { 935 | LOG_E("Couldn't find method dispatch handler"); 936 | PyErr_Print(); 937 | PyErr_Clear(); 938 | java = NULL; 939 | return -2; 940 | } 941 | LOG_V("Got method dispatch handler"); 942 | 943 | Py_DECREF(rubicon); 944 | 945 | LOG_I("Python runtime started."); 946 | return ret; 947 | } 948 | 949 | /************************************************************************** 950 | * Method to start the Python runtime. 951 | *************************************************************************/ 952 | JNIEXPORT jint JNICALL Java_org_beeware_rubicon_Python_run(JNIEnv *env, jobject thisObj, jstring module, jobjectArray args) { 953 | int ret = 0; 954 | 955 | int i; 956 | 957 | const char *module_str = (*env)->GetStringUTFChars(env, module, NULL); 958 | LOG_D("Running '%s' as __main__...", module_str); 959 | 960 | // Construct argv. 961 | int python_argc = 1; // Space for [module] 962 | if (args) { 963 | python_argc += (*env)->GetArrayLength(env, args); 964 | } 965 | 966 | wchar_t **python_argv = PyMem_RawMalloc(sizeof(wchar_t) * python_argc); 967 | python_argv[0] = Py_DecodeLocale(module_str, NULL); 968 | for (i = 1; i < python_argc; i++) { 969 | jobject arg = (*env)->GetObjectArrayElement(env, args, i - 1); 970 | const char *arg_str = (*env)->GetStringUTFChars(env, arg, NULL); 971 | python_argv[i] = Py_DecodeLocale(arg_str, NULL); 972 | } 973 | PySys_SetArgv(python_argc, python_argv); 974 | 975 | // Use `runpy._run_module_as_main` from the Python standard library to start the module. 976 | PyObject* runpy = PyImport_ImportModule("runpy"); 977 | if (runpy == NULL) { 978 | LOG_E("Could not import runpy module"); 979 | ret = 1; 980 | } 981 | 982 | PyObject* run_module_as_main; 983 | if (!ret) { 984 | run_module_as_main = PyObject_GetAttrString(runpy, "_run_module_as_main"); 985 | if (run_module_as_main == NULL) { 986 | LOG_E("Could not access runpy._run_module_as_main"); 987 | ret = 1; 988 | } 989 | } 990 | 991 | PyObject* user_module_name; 992 | if (!ret) { 993 | user_module_name = PyUnicode_FromWideChar(python_argv[0], wcslen(python_argv[0])); 994 | if (user_module_name == NULL) { 995 | LOG_E("Could not convert module name to unicode"); 996 | ret = 1; 997 | } 998 | } 999 | 1000 | PyObject* runargs; 1001 | if (!ret) { 1002 | runargs = Py_BuildValue("(Oi)", user_module_name, 0); 1003 | if (runargs == NULL) { 1004 | LOG_E("Could not create arguments for runpy._run_module_as_main"); 1005 | ret = 1; 1006 | } 1007 | } 1008 | 1009 | PyObject* result; 1010 | if (!ret) { 1011 | result = PyObject_Call(run_module_as_main, runargs, NULL); 1012 | if (result == NULL) { 1013 | if (PyErr_ExceptionMatches(PyExc_SystemExit)) { 1014 | LOG_D("Python code raised SystemExit"); 1015 | } else { 1016 | LOG_E("Application quit abnormally!"); 1017 | } 1018 | 1019 | // In the case of a SystemExit, printing the exception 1020 | // will terminate the process (including the Java VM). 1021 | PyErr_Print(); 1022 | PyErr_Clear(); 1023 | ret = 1; 1024 | } 1025 | } 1026 | 1027 | // Clean up memory allocated for args. 1028 | for (i = 0; i < python_argc; i++) { 1029 | PyMem_RawFree(python_argv[i]); 1030 | } 1031 | PyMem_RawFree(python_argv); 1032 | 1033 | return ret; 1034 | } 1035 | 1036 | /************************************************************************** 1037 | * Method to stop the Python runtime. 1038 | *************************************************************************/ 1039 | JNIEXPORT void JNICALL Java_org_beeware_rubicon_Python_stop(JNIEnv *env, jobject thisObj) { 1040 | if (java) { 1041 | LOG_D("Finalizing Python runtime..."); 1042 | Py_Finalize(); 1043 | java = NULL; 1044 | Py_XDECREF(method_handler); 1045 | LOG_I("Python runtime stopped."); 1046 | } else { 1047 | LOG_E("Python runtime doesn't appear to be running"); 1048 | } 1049 | } 1050 | 1051 | /************************************************************************** 1052 | * Implementation of the InvocationHandler used by all Python objects. 1053 | * 1054 | * This method converts the Python method invocation into a call on the 1055 | * method dispatch method that has been registered as part of the runtime. 1056 | * 1057 | * It returns NULL to Java EXCEPT if the Python code returns a number or bool. 1058 | * In those cases, it returns a boxed java.lang.Integer or java.lang.Boolean. 1059 | *************************************************************************/ 1060 | JNIEXPORT jobject JNICALL Java_org_beeware_rubicon_PythonInstance_invoke(JNIEnv *env, jobject thisObj, jobject proxy, jobject method, jobjectArray jargs) { 1061 | jclass PythonInstance = (*env)->FindClass(env, "org/beeware/rubicon/PythonInstance"); 1062 | jfieldID PythonInstance__id = (*env)->GetFieldID(env, PythonInstance, "instance", "J"); 1063 | 1064 | jclass Method = (*env)->FindClass(env, "java/lang/reflect/Method"); 1065 | jmethodID method__getName = (*env)->GetMethodID(env, Method, "getName", "()Ljava/lang/String;"); 1066 | 1067 | jobject method_name = (*env)->CallObjectMethod(env, method, method__getName); 1068 | 1069 | // `jlong` is always 64 bits. Use portable PRId64 macro for `ld` on 64-bit and `lld` on 32-bit. 1070 | jlong instance = (*env)->GetLongField(env, thisObj, PythonInstance__id); 1071 | LOG_D("Native invocation %" PRId64 " :: %s", instance, (*env)->GetStringUTFChars(env, method_name, NULL)); 1072 | 1073 | PyGILState_STATE gstate; 1074 | gstate = PyGILState_Ensure(); 1075 | 1076 | PyObject *result; 1077 | PyObject *pargs = PyTuple_New(3); 1078 | #if __SIZEOF_LONG_LONG__ == 8 1079 | // Since `jlong` is 64 bits, we check to ensure `long long` is 1080 | // also 64 bits. On x86_64, both `long` and `long long` are 1081 | // 64-bits; on x86, only `long long` is. 1082 | PyObject *pinstance = PyLong_FromLongLong(instance); 1083 | #else 1084 | #error Unable to find 8-byte integer format. 1085 | #endif 1086 | PyObject *pmethod_name = PyUnicode_FromFormat("%s", (*env)->GetStringUTFChars(env, method_name, NULL)); 1087 | PyObject *args; 1088 | 1089 | if (jargs) { 1090 | jsize argc = (*env)->GetArrayLength(env, jargs); 1091 | args = PyTuple_New(argc); 1092 | jsize i; 1093 | for (i = 0; i != argc; ++i) { 1094 | PyTuple_SET_ITEM(args, i, PyLong_FromLong((unsigned long)(*env)->GetObjectArrayElement(env, jargs, i))); 1095 | } 1096 | } else { 1097 | args = PyTuple_New(0); 1098 | } 1099 | 1100 | PyTuple_SET_ITEM(pargs, 0, pinstance); 1101 | PyTuple_SET_ITEM(pargs, 1, pmethod_name); 1102 | PyTuple_SET_ITEM(pargs, 2, args); 1103 | 1104 | result = PyObject_CallObject(method_handler, pargs); 1105 | 1106 | Py_DECREF(pargs); 1107 | 1108 | jobject ret = (jobject) NULL; 1109 | 1110 | if (result == NULL) { 1111 | LOG_E("Error invoking callback"); 1112 | PyErr_Print(); 1113 | PyErr_Clear(); 1114 | } else { 1115 | // In order to support Java interfaces that expect a `int` or `bool` return type, we check 1116 | // if the Python code returned a number or bool; if so, we convert to the appropriate boxed 1117 | // Java value. Java/JNI takes care of unboxing. 1118 | if (PyBool_Check(result)) { 1119 | jclass java_lang_boolean = (*env)->FindClass(env, "java/lang/Boolean"); 1120 | if (PyObject_IsTrue(result)) { 1121 | jfieldID true_field = (*env)->GetStaticFieldID(env, java_lang_boolean, "TRUE", "Ljava/lang/Boolean;"); 1122 | ret = (*env)->GetStaticObjectField(env, java_lang_boolean, true_field); 1123 | } else { 1124 | jfieldID false_field = (*env)->GetStaticFieldID(env, java_lang_boolean, "FALSE", "Ljava/lang/Boolean;"); 1125 | ret = (*env)->GetStaticObjectField(env, java_lang_boolean, false_field); 1126 | } 1127 | } else if (PyLong_Check(result)) { 1128 | jint result_int = (jint) PyLong_AsLong(result); 1129 | jclass java_lang_integer = (*env)->FindClass(env, "java/lang/Integer"); 1130 | jmethodID integer_constructor = (*env)->GetMethodID(env, java_lang_integer, "", "(I)V"); 1131 | if (integer_constructor == NULL) { 1132 | LOG_E("Unable to call java.lang.Integer constructor."); 1133 | } else { 1134 | ret = (*env)->NewObject(env, java_lang_integer, integer_constructor, result_int); 1135 | } 1136 | } 1137 | Py_DECREF(result); 1138 | } 1139 | LOG_D("Native invocation done."); 1140 | 1141 | PyGILState_Release(gstate); 1142 | return ret; 1143 | } 1144 | -------------------------------------------------------------------------------- /jni/rubicon.h: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #ifndef _org_beeware_rubicon 4 | #define _org_beeware_rubicon 5 | #ifdef __cplusplus 6 | extern "C" { 7 | #endif 8 | /* 9 | * Class: org_beeware_Python 10 | * Method: init 11 | * Signature: (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)I 12 | */ 13 | JNIEXPORT jint JNICALL Java_org_beeware_rubicon_Python_init(JNIEnv *, jobject, jstring, jstring, jstring); 14 | 15 | /* 16 | * Class: org_beeware_Python 17 | * Method: run 18 | * Signature: (Ljava/lang/String;)I 19 | */ 20 | JNIEXPORT jint JNICALL Java_org_beeware_rubicon_Python_run(JNIEnv *, jobject, jstring, jobjectArray); 21 | 22 | /* 23 | * Class: org_beeware_Python 24 | * Method: stop 25 | * Signature: ()V 26 | */ 27 | JNIEXPORT void JNICALL Java_org_beeware_rubicon_Python_stop(JNIEnv *, jobject); 28 | 29 | /* 30 | * Class: org_beeware_PythonInstance 31 | * Method: invoke 32 | * Signature: (Ljava/lang/Object;Ljava/lang/reflect/Method;[Ljava/lang/Object;)Ljava/lang/Object; 33 | */ 34 | JNIEXPORT jobject JNICALL Java_org_beeware_rubicon_PythonInstance_invoke(JNIEnv *, jobject, jobject, jobject, jobjectArray); 35 | 36 | #ifdef __cplusplus 37 | } 38 | #endif 39 | #endif 40 | -------------------------------------------------------------------------------- /org/beeware/rubicon/Python.java: -------------------------------------------------------------------------------- 1 | package org.beeware.rubicon; 2 | 3 | import java.lang.reflect.Proxy; 4 | 5 | import java.lang.reflect.Field; 6 | import java.lang.reflect.Method; 7 | import java.lang.reflect.Modifier; 8 | 9 | import java.util.Map; 10 | import java.util.HashMap; 11 | import java.util.HashSet; 12 | import java.util.Set; 13 | 14 | public class Python { 15 | /** 16 | * A 2 level map of Class: Method name: Method for instance methods 17 | */ 18 | private static Map>> _instanceMethods; 19 | 20 | /** 21 | * A 2 level map of Class: Method name: Method for static methods 22 | */ 23 | private static Map>> _staticMethods; 24 | 25 | static { 26 | System.out.println("LOAD LIBRARY"); 27 | System.loadLibrary("rubicon"); 28 | 29 | _instanceMethods = new HashMap>>(); 30 | _staticMethods = new HashMap>>(); 31 | } 32 | 33 | /** 34 | * Create a proxy implementation that directs towards a Python instance. 35 | * 36 | * @param pythonHome The value for the PYTHONHOME environment variable 37 | * @param pythonPath The value for the PYTHONPATH environment variable 38 | * @param rubiconLib The path to the Rubicon integration library. This library 39 | * will be explictly loaded as part of the startup of the 40 | * Python integration library. If null, it is assumed that the 41 | * system LD_LIBRARY_PATH (or equivalent) will contain the 42 | * Rubicon library 43 | * @return The proxy object. 44 | */ 45 | public static native int init(String pythonHome, String pythonPath, String rubiconLib); 46 | 47 | /** 48 | * Run a Python module as __main__. 49 | * 50 | * @param module The name of the Python module to run. Dots are OK, e.g., myapp.main. 51 | * @param args The value for Python's sys.argv. 52 | * @return 0 on success; non-zero on failure. 53 | */ 54 | public static native int run(String script, String[] args); 55 | 56 | /** 57 | * Stop the Python runtime. 58 | */ 59 | public static native void stop(); 60 | 61 | /** 62 | * Create a proxy implementation that directs towards a Python instance. 63 | * 64 | * @param cls The interface/class that is to be proxied 65 | * @param instance The unique Python ID of the instance to be proxied. 66 | * @return The proxy object. 67 | */ 68 | public static Object proxy(Class cls, long instance) { 69 | Object pinstance = Proxy.newProxyInstance(cls.getClassLoader(), new Class[] { cls }, 70 | new PythonInstance(instance)); 71 | return pinstance; 72 | } 73 | 74 | /** 75 | * Retrieve the list of methods on a class with a specific name. 76 | * 77 | * This is used to implement on-demand polymorphism checks. 78 | * 79 | * @param cls The class to be interrogated 80 | * @param name The name of the method to retrieve 81 | * @param isStatic If True, return only static methods; otherwise, return 82 | * instance methods. 83 | * @return The array of Method instances matching the provided name. 84 | */ 85 | public static Method[] getMethods(Class cls, String name, boolean isStatic) { 86 | Map> methodMap; 87 | 88 | if (isStatic) { 89 | methodMap = _staticMethods.get(cls); 90 | } else { 91 | methodMap = _instanceMethods.get(cls); 92 | } 93 | 94 | if (methodMap == null) { 95 | Map> instanceNameMap = new HashMap>(); 96 | Map> staticNameMap = new HashMap>(); 97 | 98 | for (Method method : cls.getMethods()) { 99 | int modifiers = method.getModifiers(); 100 | if (Modifier.isPublic(modifiers)) { 101 | if (Modifier.isStatic(modifiers)) { 102 | Set alternatives = staticNameMap.get(method.getName()); 103 | if (alternatives == null) { 104 | alternatives = new HashSet(); 105 | staticNameMap.put(method.getName(), alternatives); 106 | } 107 | 108 | alternatives.add(method); 109 | } else { 110 | Set alternatives = instanceNameMap.get(method.getName()); 111 | if (alternatives == null) { 112 | alternatives = new HashSet(); 113 | instanceNameMap.put(method.getName(), alternatives); 114 | } 115 | 116 | alternatives.add(method); 117 | } 118 | } 119 | } 120 | 121 | _instanceMethods.put(cls, instanceNameMap); 122 | _staticMethods.put(cls, staticNameMap); 123 | 124 | if (isStatic) { 125 | methodMap = staticNameMap; 126 | } else { 127 | methodMap = instanceNameMap; 128 | } 129 | } 130 | 131 | return methodMap.get(name).toArray(new Method[0]); 132 | } 133 | 134 | /** 135 | * Retrieve the list of methods on a class with a specific name. 136 | * 137 | * This is used to implement on-demand polymorphism checks. 138 | * 139 | * @param cls The class to be interrogated 140 | * @param name The name of the method to retrieve 141 | * @param isStatic If True, return only static fields; otherwise, return 142 | * instance fields. 143 | * @return The field matching the provided name; null if no field with the 144 | * provided name exists 145 | */ 146 | public static Field getField(Class cls, String name, boolean isStatic) { 147 | try { 148 | Field field = cls.getField(name); 149 | int modifiers = field.getModifiers(); 150 | 151 | if (Modifier.isStatic(modifiers) == isStatic) { 152 | if (Modifier.isPublic(modifiers)) { 153 | return field; 154 | } else { 155 | // Field matching name exists, but it isn't public. 156 | return null; 157 | } 158 | } else { 159 | // Field matching name exists, but static qualifier doesn't match requested 160 | // field. 161 | return null; 162 | } 163 | } catch (NoSuchFieldException e) { 164 | // No field matching requested name. 165 | return null; 166 | } 167 | } 168 | } 169 | -------------------------------------------------------------------------------- /org/beeware/rubicon/PythonInstance.java: -------------------------------------------------------------------------------- 1 | package org.beeware.rubicon; 2 | 3 | import java.lang.reflect.InvocationHandler; 4 | import java.lang.reflect.Method; 5 | 6 | 7 | public class PythonInstance implements InvocationHandler { 8 | /** 9 | * The Python instance ID. 10 | */ 11 | public long instance; 12 | 13 | /** 14 | * A representation of a Python object on the Java side. 15 | * 16 | * @param inst The Python instance ID of the object. 17 | */ 18 | public PythonInstance(long inst) { 19 | instance = inst; 20 | } 21 | 22 | /** 23 | * Invoke a method on the Python object. 24 | * 25 | * When used as a proxy, this enables Python C API calls to be used to 26 | * satisfy a 27 | * 28 | * @param inst The Java proxy of the Python object. 29 | * @param method The Java method to invoke. 30 | * @param args The array of arguments to be passed to the method. 31 | * @return The return value from the Python method. 32 | */ 33 | public native Object invoke(Object proxy, Method method, Object[] args) throws Throwable; 34 | } 35 | -------------------------------------------------------------------------------- /org/beeware/rubicon/test/AbstractCallback.java: -------------------------------------------------------------------------------- 1 | package org.beeware.rubicon.test; 2 | 3 | 4 | public abstract class AbstractCallback implements ICallback { 5 | public AbstractCallback() { 6 | } 7 | 8 | public void poke(Example example, int value) { 9 | example.set_int_field(2 * value); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /org/beeware/rubicon/test/AddOne.java: -------------------------------------------------------------------------------- 1 | package org.beeware.rubicon.test; 2 | 3 | 4 | public class AddOne { 5 | public int addOne(ICallbackInt dataSource, Example example) { 6 | int val = dataSource.getInt(example); 7 | return val + 1; 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /org/beeware/rubicon/test/BaseExample.java: -------------------------------------------------------------------------------- 1 | package org.beeware.rubicon.test; 2 | 3 | 4 | public class BaseExample { 5 | static public int static_base_int_field = 1; 6 | 7 | static public void set_static_base_int_field(int value) { 8 | static_base_int_field = value; 9 | } 10 | 11 | static public int get_static_base_int_field() { 12 | return static_base_int_field; 13 | } 14 | 15 | public int base_int_field; 16 | 17 | public BaseExample() { 18 | base_int_field = 2; 19 | } 20 | 21 | public BaseExample(int value) { 22 | base_int_field = value; 23 | } 24 | 25 | public void set_base_int_field(int value) { 26 | base_int_field = value; 27 | } 28 | 29 | public int get_base_int_field() { 30 | return base_int_field; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /org/beeware/rubicon/test/Example.java: -------------------------------------------------------------------------------- 1 | package org.beeware.rubicon.test; 2 | 3 | import java.lang.Math; 4 | 5 | import org.beeware.rubicon.Python; 6 | 7 | 8 | public class Example extends BaseExample { 9 | 10 | /* Static fields and methods */ 11 | static public int static_int_field = 11; 12 | 13 | static public void set_static_int_field(int value) { 14 | static_int_field = value; 15 | } 16 | 17 | static public int get_static_int_field() { 18 | return static_int_field; 19 | } 20 | 21 | static public long static_long_field = 0; 22 | 23 | static public void set_static_long_field(long value) { 24 | static_long_field = value; 25 | } 26 | 27 | static public long get_static_long_field() { 28 | return static_long_field; 29 | } 30 | 31 | static public int sum_all_ints(int[] numbers) { 32 | int sum = 0; 33 | for (int number : numbers) { 34 | sum += number; 35 | } 36 | return sum; 37 | } 38 | 39 | static public double sum_all_doubles(double[] numbers) { 40 | double sum = 0; 41 | for (double number : numbers) { 42 | sum += number; 43 | } 44 | return sum; 45 | } 46 | 47 | static public float sum_all_floats(float[] numbers) { 48 | float sum = 0; 49 | for (float number : numbers) { 50 | sum += number; 51 | } 52 | return sum; 53 | } 54 | 55 | static public boolean combine_booleans_by_and(boolean[] values) { 56 | boolean result = true; 57 | for (boolean value : values) { 58 | result = result && value; 59 | } 60 | return result; 61 | } 62 | 63 | static public int xor_all_bytes(byte[] values) { 64 | int result = 0; 65 | for (byte value : values) { 66 | result = result ^ value; 67 | } 68 | return result; 69 | } 70 | 71 | /* An inner enumerated type */ 72 | public enum Stuff { 73 | FOO, BAR, WHIZ; 74 | } 75 | 76 | /* Public member fields and method */ 77 | public int int_field; 78 | private ICallback callback; 79 | public Thing theThing; 80 | 81 | /* Polymorphic constructors */ 82 | public Example() { 83 | super(22); 84 | int_field = 33; 85 | } 86 | 87 | public Example(int value) { 88 | super(44); 89 | int_field = value; 90 | } 91 | 92 | public Example(int base_value, int value) { 93 | super(base_value); 94 | int_field = value; 95 | } 96 | 97 | protected Example(String value) { 98 | // A protected constructor - it exists, but can't be accessed by Python. 99 | super(999); 100 | } 101 | 102 | /* Accessor/mutator methods */ 103 | public void set_int_field(int value) { 104 | int_field = value; 105 | } 106 | 107 | public int get_int_field() { 108 | return int_field; 109 | } 110 | 111 | /* Float/Double argument/return value handling */ 112 | public float area_of_square(float size) { 113 | return size * size; 114 | } 115 | 116 | public double area_of_circle(double diameter) { 117 | return diameter * Math.PI; 118 | } 119 | 120 | /* Enum argument handling */ 121 | public String label(Stuff value) { 122 | switch (value) 123 | { 124 | case FOO: return "Foo"; 125 | case BAR: return "Bar"; 126 | case WHIZ: return "Whiz"; 127 | default: return "Unknown"; 128 | } 129 | } 130 | 131 | /* Handling of object references. */ 132 | public void set_thing(Thing thing) { 133 | theThing = thing; 134 | } 135 | 136 | public Thing get_thing() { 137 | return theThing; 138 | } 139 | 140 | public Object get_generic_thing() { 141 | return theThing; 142 | } 143 | 144 | /* String argument/return value handling */ 145 | public String duplicate_string(String in) { 146 | return in + in; 147 | } 148 | 149 | /* Polymorphism handling */ 150 | public String doubler(String in) { 151 | if (in == null) { 152 | return "Can't double NULL strings"; 153 | } 154 | return in + in; 155 | } 156 | 157 | public int doubler(int in) { 158 | return in + in; 159 | } 160 | 161 | public long doubler(long in) { 162 | return in + in; 163 | } 164 | 165 | public boolean [] doubler(boolean [] in) { 166 | boolean [] out = new boolean[in.length * 2]; 167 | for (int i = 0; i < out.length; i++) { 168 | out[i] = in[i / 2]; 169 | } 170 | return out; 171 | } 172 | 173 | public byte [] doubler(byte [] in) { 174 | byte [] out = new byte[in.length * 2]; 175 | for (int i = 0; i < out.length; i++) { 176 | out[i] = in[i / 2]; 177 | } 178 | return out; 179 | } 180 | 181 | public short [] doubler(short [] in) { 182 | short [] out = new short[in.length * 2]; 183 | for (int i = 0; i < out.length; i++) { 184 | out[i] = in[i / 2]; 185 | } 186 | return out; 187 | } 188 | 189 | public int [] doubler(int [] in) { 190 | int [] out = new int[in.length * 2]; 191 | for (int i = 0; i < out.length; i++) { 192 | out[i] = in[i / 2]; 193 | } 194 | return out; 195 | } 196 | 197 | public long [] doubler(long [] in) { 198 | long [] out = new long[in.length * 2]; 199 | for (int i = 0; i < out.length; i++) { 200 | out[i] = in[i / 2]; 201 | } 202 | return out; 203 | } 204 | 205 | public float [] doubler(float [] in) { 206 | float [] out = new float[in.length * 2]; 207 | for (int i = 0; i < out.length; i++) { 208 | out[i] = in[i / 2]; 209 | } 210 | return out; 211 | } 212 | 213 | public double [] doubler(double [] in) { 214 | double [] out = new double[in.length * 2]; 215 | for (int i = 0; i < out.length; i++) { 216 | out[i] = in[i / 2]; 217 | } 218 | return out; 219 | } 220 | 221 | public String [] doubler(String [] in) { 222 | String [] out = new String[in.length * 2]; 223 | for (int i = 0; i < out.length; i++) { 224 | out[i] = in[i / 2]; 225 | } 226 | return out; 227 | } 228 | 229 | public Thing [] doubler(Thing [] in) { 230 | Thing [] out = new Thing[in.length * 2]; 231 | for (int i = 0; i < out.length; i++) { 232 | out[i] = in[i / 2]; 233 | } 234 | return out; 235 | } 236 | 237 | public static String tripler(String in) { 238 | return in + in + in; 239 | } 240 | 241 | public static int tripler(int in) { 242 | return in + in + in; 243 | } 244 | 245 | public static long tripler(long in) { 246 | return in + in + in; 247 | } 248 | 249 | /* Handling long argument lists */ 250 | public String combiner(int x, String name, Thing thing, ICallback callback, int [] values) { 251 | if (name == null) { 252 | name = ""; 253 | } 254 | 255 | String thing_label; 256 | if (thing == null) { 257 | thing_label = ""; 258 | } else { 259 | thing_label = thing.toString(); 260 | } 261 | 262 | String callback_label; 263 | if (callback == null) { 264 | callback_label = ""; 265 | } else { 266 | callback_label = "There is a callback"; 267 | } 268 | 269 | String value_count; 270 | if (values == null) { 271 | value_count = ""; 272 | } else { 273 | value_count = "There are " + values.length + " values"; 274 | } 275 | 276 | return x + "::" + name + "::" + thing_label + "::" + callback_label + "::" + value_count; 277 | } 278 | 279 | /* Interface visiblity */ 280 | protected void invisible_method(int value) {} 281 | protected static void static_invisible_method(int value) {} 282 | protected int invisible_field; 283 | protected static int static_invisible_field; 284 | 285 | /* Callback handling */ 286 | public void set_callback(ICallback cb) { 287 | callback = cb; 288 | } 289 | 290 | public void test_poke(int value) { 291 | callback.poke(this, value); 292 | } 293 | 294 | public void test_peek(int value) { 295 | callback.peek(this, value); 296 | } 297 | 298 | /* General utility - converting objects to string */ 299 | public String toString() { 300 | return "This is a Java Example object"; 301 | } 302 | 303 | /* Inner classes */ 304 | public class Inner { 305 | public final static int INNER_CONSTANT = 1234; 306 | 307 | public Inner() { 308 | } 309 | 310 | int the_answer(boolean correct) { 311 | if (correct) { 312 | return 42; 313 | } else { 314 | return 54; 315 | } 316 | } 317 | } 318 | } 319 | -------------------------------------------------------------------------------- /org/beeware/rubicon/test/ICallback.java: -------------------------------------------------------------------------------- 1 | package org.beeware.rubicon.test; 2 | 3 | 4 | public interface ICallback { 5 | public void poke(Example example, int value); 6 | 7 | public void peek(Example example, int value); 8 | } 9 | -------------------------------------------------------------------------------- /org/beeware/rubicon/test/ICallbackBool.java: -------------------------------------------------------------------------------- 1 | package org.beeware.rubicon.test; 2 | 3 | 4 | public interface ICallbackBool { 5 | public boolean getBool(); 6 | } 7 | -------------------------------------------------------------------------------- /org/beeware/rubicon/test/ICallbackInt.java: -------------------------------------------------------------------------------- 1 | package org.beeware.rubicon.test; 2 | 3 | 4 | public interface ICallbackInt { 5 | public int getInt(Example example); 6 | } 7 | -------------------------------------------------------------------------------- /org/beeware/rubicon/test/Test.java: -------------------------------------------------------------------------------- 1 | package org.beeware.rubicon.test; 2 | 3 | import org.beeware.rubicon.Python; 4 | 5 | public class Test { 6 | public static void main(String[] args) { 7 | if (Python.init(null, ".", null) != 0) { 8 | System.err.println("Got an error initializing Python"); 9 | System.exit(1); 10 | } 11 | 12 | if (Python.run("tests.runner", args) != 0) { 13 | System.err.println("Got an error running Python script"); 14 | System.exit(1); 15 | } 16 | 17 | Python.stop(); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /org/beeware/rubicon/test/Thing.java: -------------------------------------------------------------------------------- 1 | package org.beeware.rubicon.test; 2 | 3 | import org.beeware.rubicon.Python; 4 | 5 | 6 | public class Thing { 7 | static public int static_int_field = 11; 8 | 9 | public String name; 10 | public int count; 11 | 12 | public Thing(String n) { 13 | name = n; 14 | count = -1; 15 | } 16 | 17 | public Thing(String n, int c) { 18 | count = c; 19 | name = n + ' ' + c; 20 | } 21 | 22 | public String toString() { 23 | return name; 24 | } 25 | 26 | public int currentCount() { 27 | return count; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /org/beeware/rubicon/test/TruthInverter.java: -------------------------------------------------------------------------------- 1 | package org.beeware.rubicon.test; 2 | 3 | public class TruthInverter { 4 | public boolean invert(ICallbackBool bool_maker) { 5 | return !(bool_maker.getBool()); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = [ 3 | "setuptools >= 46.4.0", 4 | "wheel >= 0.32.0", 5 | ] 6 | build-backend = "setuptools.build_meta" 7 | 8 | [tool.towncrier] 9 | directory = "changes" 10 | package = "rubicon.java" 11 | filename = "docs/background/releases.rst" 12 | title_format = "{version} ({project_date})" 13 | template = "changes/template.rst" 14 | -------------------------------------------------------------------------------- /rubicon/java/__init__.py: -------------------------------------------------------------------------------- 1 | from .api import * # noqa; F401, F403 2 | from .jni import * # noqa; F401, F403 3 | from .types import * # noqa; F401, F403 4 | 5 | __version__ = '0.2.6' 6 | -------------------------------------------------------------------------------- /rubicon/java/android_events.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | import asyncio.base_events 3 | import asyncio.events 4 | import asyncio.log 5 | import heapq 6 | import selectors 7 | import sys 8 | import threading 9 | 10 | from . import JavaClass, JavaInterface 11 | 12 | Looper = JavaClass("android/os/Looper") 13 | Handler = JavaClass("android/os/Handler") 14 | OnFileDescriptorEventListener = JavaInterface( 15 | "android/os/MessageQueue$OnFileDescriptorEventListener" 16 | ) 17 | FileDescriptor = JavaClass("java/io/FileDescriptor") 18 | Runnable = JavaInterface("java/lang/Runnable") 19 | 20 | # Some methods in this file are based on CPython's implementation. 21 | # Per https://github.com/python/cpython/blob/master/LICENSE , re-use is permitted 22 | # via the Python Software Foundation License Version 2, which includes inclusion 23 | # into this project under its BSD license terms so long as we retain this copyright notice: 24 | # Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013, 25 | # 2014, 2015, 2016, 2017, 2018, 2019, 2020 Python Software Foundation; All Rights Reserved. 26 | 27 | 28 | class AndroidEventLoop(asyncio.SelectorEventLoop): 29 | # `AndroidEventLoop` exists to support starting the Python event loop cooperatively with 30 | # the built-in Android event loop. Since it's cooperative, it has a `run_forever_cooperatively()` 31 | # method which returns immediately. This is is different from the parent class's `run_forever()`, 32 | # which blocks. 33 | # 34 | # In some cases, for simplicity of implementation, this class reaches into the internals of the 35 | # parent and grandparent classes. 36 | # 37 | # A Python event loop handles two kinds of tasks. It needs to run delayed tasks after waiting 38 | # the right amount of time, and it needs to do I/O when file descriptors are ready for I/O. 39 | # 40 | # `SelectorEventLoop` uses an approach we **cannot** use: it calls the `select()` method 41 | # to block waiting for specific file descriptors to be come ready for I/O, or a timeout 42 | # corresponding to the soonest delayed task, whichever occurs sooner. 43 | # 44 | # To handle delayed tasks, `AndroidEventLoop` asks the Android event loop to wake it up when 45 | # its soonest delayed task is ready. To accomplish this, it relies on a `SelectorEventLoop` 46 | # implementation detail: `_scheduled` is a collection of tasks sorted by soonest wakeup time. 47 | # 48 | # To handle waking up when it's possible to do I/O, `AndroidEventLoop` will register file descriptors 49 | # with the Android event loop so the platform can wake it up accordingly. It does not do this yet. 50 | def __init__(self): 51 | # Tell the parent constructor to use our custom Selector. 52 | selector = AndroidSelector(self) 53 | super().__init__(selector) 54 | # Create placeholders for lazily-created objects. 55 | self.android_interop = AndroidInterop() 56 | 57 | # Override parent `run_in_executor()` to run all code synchronously. This disables the 58 | # `executor` thread that typically exists in event loops. The event loop itself relies 59 | # on `run_in_executor()` for DNS lookups. In the future, we can restore `run_in_executor()`. 60 | async def run_in_executor(self, executor, func, *args): 61 | return func(*args) 62 | 63 | # Override parent `_call_soon()` to ensure Android wakes us up to do the delayed task. 64 | def _call_soon(self, callback, args, context): 65 | ret = super()._call_soon(callback, args, context) 66 | self.enqueue_android_wakeup_for_delayed_tasks() 67 | return ret 68 | 69 | # Override parent `_add_callback()` to ensure Android wakes us up to do the delayed task. 70 | def _add_callback(self, handle): 71 | ret = super()._add_callback(handle) 72 | self.enqueue_android_wakeup_for_delayed_tasks() 73 | return ret 74 | 75 | def run_forever_cooperatively(self): 76 | """Configure the event loop so it is started, doing as little work as possible to 77 | ensure that. Most Android interop objects are created lazily so that the cost of 78 | event loop interop is not paid by apps that don't use the event loop.""" 79 | # Based on `BaseEventLoop.run_forever()` in CPython. 80 | if self.is_running(): 81 | raise RuntimeError("Refusing to start since loop is already running.") 82 | if self._closed: 83 | raise RuntimeError("Event loop is closed. Create a new object.") 84 | self._set_coroutine_origin_tracking(self._debug) 85 | self._thread_id = threading.get_ident() 86 | 87 | self._old_agen_hooks = sys.get_asyncgen_hooks() 88 | sys.set_asyncgen_hooks( 89 | firstiter=self._asyncgen_firstiter_hook, 90 | finalizer=self._asyncgen_finalizer_hook, 91 | ) 92 | asyncio.events._set_running_loop(self) 93 | 94 | def enqueue_android_wakeup_for_delayed_tasks(self): 95 | """Ask Android to wake us up when delayed tasks are ready to be handled. 96 | 97 | Since this is effectively the actual event loop, it also handles stopping the loop.""" 98 | # If we are supposed to stop, actually stop. 99 | if self._stopping: 100 | self._stopping = False 101 | self._thread_id = None 102 | asyncio.events._set_running_loop(None) 103 | self._set_coroutine_origin_tracking(False) 104 | sys.set_asyncgen_hooks(*self._old_agen_hooks) 105 | # Remove Android event loop interop objects. 106 | self.android_interop = None 107 | return 108 | 109 | # If we have actually already stopped, then do nothing. 110 | if self._thread_id is None: 111 | return 112 | 113 | timeout = self._get_next_delayed_task_wakeup() 114 | if timeout is None: 115 | # No delayed tasks. 116 | return 117 | 118 | # Ask Android to wake us up to run delayed tasks. Running delayed tasks also 119 | # checks for other tasks that require wakeup by calling this method. The fact that 120 | # running delayed tasks can trigger the next wakeup is what makes this event loop a "loop." 121 | self.android_interop.call_later( 122 | self.run_delayed_tasks, timeout * 1000, 123 | ) 124 | 125 | def _set_coroutine_origin_tracking(self, debug): 126 | # If running on Python 3.7 or 3.8, integrate with upstream event loop's debug feature, allowing 127 | # unawaited coroutines to have some useful info logged. See https://bugs.python.org/issue32591 128 | if hasattr(super(), "_set_coroutine_origin_tracking"): 129 | super()._set_coroutine_origin_tracking(debug) 130 | 131 | def _get_next_delayed_task_wakeup(self): 132 | """Compute the time to sleep before we should be woken up to handle delayed tasks.""" 133 | # This is based heavily on the CPython's implementation of `BaseEventLoop._run_once()` 134 | # before it blocks on `select()`. 135 | _MIN_SCHEDULED_TIMER_HANDLES = 100 136 | _MIN_CANCELLED_TIMER_HANDLES_FRACTION = 0.5 137 | MAXIMUM_SELECT_TIMEOUT = 24 * 3600 138 | 139 | sched_count = len(self._scheduled) 140 | if ( 141 | sched_count > _MIN_SCHEDULED_TIMER_HANDLES 142 | and self._timer_cancelled_count / sched_count 143 | > _MIN_CANCELLED_TIMER_HANDLES_FRACTION 144 | ): 145 | # Remove delayed calls that were cancelled if their number 146 | # is too high 147 | new_scheduled = [] 148 | for handle in self._scheduled: 149 | if handle._cancelled: 150 | handle._scheduled = False 151 | else: 152 | new_scheduled.append(handle) 153 | 154 | heapq.heapify(new_scheduled) 155 | self._scheduled = new_scheduled 156 | self._timer_cancelled_count = 0 157 | else: 158 | # Remove delayed calls that were cancelled from head of queue. 159 | while self._scheduled and self._scheduled[0]._cancelled: 160 | self._timer_cancelled_count -= 1 161 | handle = heapq.heappop(self._scheduled) 162 | handle._scheduled = False 163 | 164 | timeout = None 165 | if self._ready or self._stopping: 166 | if self._debug: 167 | print("AndroidEventLoop: self.ready is", self._ready) 168 | timeout = 0 169 | elif self._scheduled: 170 | # Compute the desired timeout. 171 | when = self._scheduled[0]._when 172 | timeout = min(max(0, when - self.time()), MAXIMUM_SELECT_TIMEOUT) 173 | 174 | return timeout 175 | 176 | def run_delayed_tasks(self): 177 | """Android-specific: Run any delayed tasks that have become ready. Additionally, check if 178 | there are more delayed tasks to execute in the future; if so, schedule the next wakeup.""" 179 | # Based heavily on `BaseEventLoop._run_once()` from CPython -- specifically, the part 180 | # after blocking on `select()`. 181 | # Handle 'later' callbacks that are ready. 182 | end_time = self.time() + self._clock_resolution 183 | while self._scheduled: 184 | handle = self._scheduled[0] 185 | if handle._when >= end_time: 186 | break 187 | handle = heapq.heappop(self._scheduled) 188 | handle._scheduled = False 189 | self._ready.append(handle) 190 | 191 | # This is the only place where callbacks are actually *called*. 192 | # All other places just add them to ready. 193 | # Note: We run all currently scheduled callbacks, but not any 194 | # callbacks scheduled by callbacks run this time around -- 195 | # they will be run the next time (after another I/O poll). 196 | # Use an idiom that is thread-safe without using locks. 197 | ntodo = len(self._ready) 198 | for i in range(ntodo): 199 | handle = self._ready.popleft() 200 | if handle._cancelled: 201 | continue 202 | if self._debug: 203 | try: 204 | self._current_handle = handle 205 | t0 = self.time() 206 | handle._run() 207 | dt = self.time() - t0 208 | if dt >= self.slow_callback_duration: 209 | asyncio.log.logger.warning( 210 | "Executing %s took %.3f seconds", 211 | asyncio.base_events._format_handle(handle), 212 | dt, 213 | ) 214 | finally: 215 | self._current_handle = None 216 | else: 217 | handle._run() 218 | handle = None # Needed to break cycles when an exception occurs. 219 | 220 | # End code borrowed from CPython, within this method. 221 | self.enqueue_android_wakeup_for_delayed_tasks() 222 | 223 | 224 | class AndroidInterop: 225 | """Encapsulate details of Android event loop cooperation.""" 226 | 227 | def __init__(self): 228 | # `_runnable_by_fn` is a one-to-one mapping from Python callables to Java Runnables. 229 | # This allows us to avoid creating more than one Java object per Python callable, which 230 | # would prevent removeCallbacks from working. 231 | self._runnable_by_fn = {} 232 | # The handler must be created on the Android UI thread. 233 | self.handler = Handler() 234 | 235 | def get_or_create_runnable(self, fn): 236 | if fn in self._runnable_by_fn: 237 | return self._runnable_by_fn[fn] 238 | 239 | self._runnable_by_fn[fn] = PythonRunnable(fn) 240 | return self._runnable_by_fn[fn] 241 | 242 | def call_later(self, fn, timeout_millis): 243 | """Enqueue a Python callable `fn` to be run after `timeout_millis` milliseconds.""" 244 | # Coerce timeout_millis to an integer since postDelayed() takes an integer (jlong). 245 | runnable = self.get_or_create_runnable(fn) 246 | self.handler.removeCallbacks(runnable) 247 | self.handler.postDelayed(runnable, int(timeout_millis)) 248 | 249 | 250 | class PythonRunnable(Runnable): 251 | """Bind a specific Python callable in a Java `Runnable`.""" 252 | 253 | def __init__(self, fn): 254 | super().__init__() 255 | self._fn = fn 256 | 257 | def run(self): 258 | self._fn() 259 | 260 | 261 | class AndroidSelector(selectors.SelectSelector): 262 | """Subclass of selectors.Selector which cooperates with the Android event loop 263 | to learn when file descriptors become ready for I/O. 264 | 265 | AndroidSelector's `select()` raises NotImplementedError; see its comments.""" 266 | 267 | def __init__(self, loop): 268 | super().__init__() 269 | self.loop = loop 270 | # Lazily-created AndroidSelectorFileDescriptorEventsListener. 271 | self._file_descriptor_event_listener = None 272 | # Keep a `_debug` flag so that a developer can modify it for more debug printing. 273 | self._debug = False 274 | 275 | @property 276 | def file_descriptor_event_listener(self): 277 | if self._file_descriptor_event_listener is not None: 278 | return self._file_descriptor_event_listener 279 | self._file_descriptor_event_listener = AndroidSelectorFileDescriptorEventsListener( 280 | android_selector=self, 281 | ) 282 | return self._file_descriptor_event_listener 283 | 284 | @property 285 | def message_queue(self): 286 | return Looper.getMainLooper().getQueue() 287 | 288 | # File descriptors can be registered and unregistered by the event loop. 289 | # The events for which we listen can be modified. For register & unregister, 290 | # we mostly rely on the parent class. For modify(), the parent class calls 291 | # unregister() and register(), so we rely on that as well. 292 | 293 | def register(self, fileobj, events, data=None): 294 | if self._debug: 295 | print( 296 | "register() fileobj={fileobj} events={events} data={data}".format( 297 | fileobj=fileobj, events=events, data=data 298 | ) 299 | ) 300 | ret = super().register(fileobj, events, data=data) 301 | self.register_with_android(fileobj, events) 302 | return ret 303 | 304 | def unregister(self, fileobj): 305 | self.message_queue.removeOnFileDescriptorEventListener(_create_java_fd(fileobj)) 306 | return super().unregister(fileobj) 307 | 308 | def reregister_with_android_soon(self, fileobj): 309 | def _reregister(): 310 | # If the fileobj got unregistered, exit early. 311 | key = self._key_from_fd(fileobj) 312 | if key is None: 313 | if self._debug: 314 | print( 315 | "reregister_with_android_soon reregister_temporarily_ignored_fd exiting early; key=None" 316 | ) 317 | return 318 | if self._debug: 319 | print( 320 | "reregister_with_android_soon reregistering key={key}".format( 321 | key=key 322 | ) 323 | ) 324 | self.register_with_android(key.fd, key.events) 325 | 326 | # Use `call_later(0, fn)` to ensure the Python event loop runs to completion before re-registering. 327 | self.loop.call_later(0, _reregister) 328 | 329 | def register_with_android(self, fileobj, events): 330 | if self._debug: 331 | print( 332 | "register_with_android() fileobj={fileobj} events={events}".format( 333 | fileobj=fileobj, events=events 334 | ) 335 | ) 336 | # `events` is a bitset comprised of `selectors.EVENT_READ` and `selectors.EVENT_WRITE`. 337 | # Register this FD for read and/or write events from Android. 338 | self.message_queue.addOnFileDescriptorEventListener( 339 | _create_java_fd(fileobj), 340 | events, # Passing `events` as-is because Android and Python use the same values for read & write events. 341 | self.file_descriptor_event_listener, 342 | ) 343 | 344 | def handle_fd_wakeup(self, fd, events): 345 | """Accept a FD and the events that it is ready for (read and/or write). 346 | 347 | Filter the events to just those that are registered, then notify the loop.""" 348 | key = self._key_from_fd(fd) 349 | if key is None: 350 | print( 351 | "Warning: handle_fd_wakeup: wakeup for unregistered fd={fd}".format( 352 | fd=fd 353 | ) 354 | ) 355 | return 356 | 357 | key_event_pairs = [] 358 | for event_type in (selectors.EVENT_READ, selectors.EVENT_WRITE): 359 | if events & event_type and key.events & event_type: 360 | key_event_pairs.append((key, event_type)) 361 | if key_event_pairs: 362 | if self._debug: 363 | print( 364 | "handle_fd_wakeup() calling parent for key_event_pairs={key_event_pairs}".format( 365 | key_event_pairs=key_event_pairs 366 | ) 367 | ) 368 | # Call superclass private method to notify. 369 | self.loop._process_events(key_event_pairs) 370 | else: 371 | print( 372 | "Warning: handle_fd_wakeup(): unnecessary wakeup fd={fd} events={events} key={key}".format( 373 | fd=fd, events=events, key=key 374 | ) 375 | ) 376 | 377 | # This class declines to implement the `select()` method, purely as 378 | # a safety mechanism. On Android, this would be an error -- it would result 379 | # in the app freezing, triggering an App Not Responding pop-up from the 380 | # platform, and the user killing the app. 381 | # 382 | # Instead, the AndroidEventLoop cooperates with the native Android event 383 | # loop to be woken up to get work done as needed. 384 | def select(self, *args, **kwargs): 385 | raise NotImplementedError("AndroidSelector refuses to select(); see comments.") 386 | 387 | 388 | class AndroidSelectorFileDescriptorEventsListener(OnFileDescriptorEventListener): 389 | """Notify an `AndroidSelector` instance when file descriptors become readable/writable.""" 390 | 391 | def __init__(self, android_selector): 392 | super().__init__() 393 | self.android_selector = android_selector 394 | # Keep a `_debug` flag so that a developer can modify it for more debug printing. 395 | self._debug = False 396 | 397 | def onFileDescriptorEvents(self, fd_obj, events): 398 | """Receive a Java FileDescriptor object and notify the Python event loop that the FD 399 | is ready for read and/or write. 400 | 401 | As an implementation detail, this relies on the fact that Android EVENT_INPUT and Python 402 | selectors.EVENT_READ have the same value (1) and Android EVENT_OUTPUT and Python 403 | selectors.EVENT_WRITE have the same value (2).""" 404 | # Call hidden (non-private) method to get the numeric FD, so we can pass that to Python. 405 | fd = getattr(fd_obj, "getInt$")() 406 | if self._debug: 407 | print( 408 | "onFileDescriptorEvents woke up for fd={fd} events={events}".format( 409 | fd=fd, events=events 410 | ) 411 | ) 412 | # Tell the Python event loop that the FD is ready for read and/or write. 413 | self.android_selector.handle_fd_wakeup(fd, events) 414 | # Tell Android we don't want any more wake-ups from this FD until the event loop runs. 415 | # To do that, we return 0. 416 | # 417 | # We also need Python to request wake-ups once the event loop has finished. 418 | self.android_selector.reregister_with_android_soon(fd) 419 | return 0 420 | 421 | 422 | def _create_java_fd(int_fd): 423 | """Given a numeric file descriptor, create a `java.io.FileDescriptor` object.""" 424 | # On Android, the class exposes hidden (non-private) methods `getInt$()` and `setInt$()`. Because 425 | # they aren't valid Python identifier names, we need to use `getattr()` to grab them. 426 | # See e.g. https://android.googlesource.com/platform/prebuilts/fullsdk/sources/android-28/+/refs/heads/master/java/io/FileDescriptor.java#149 # noqa: E501 427 | java_fd = FileDescriptor() 428 | getattr(java_fd, "setInt$")(int_fd) 429 | return java_fd 430 | -------------------------------------------------------------------------------- /rubicon/java/jni.py: -------------------------------------------------------------------------------- 1 | import os 2 | from ctypes import c_char_p, cast, cdll 3 | 4 | from .types import ( 5 | jarray, jboolean, jboolean_p, jbooleanArray, 6 | jbyte, jbyte_p, jbyteArray, jchar, jclass, 7 | jdouble, jdouble_p, jdoubleArray, jfieldID, jfloat, jfloat_p, jfloatArray, 8 | jint, jint_p, jintArray, jlong, jlong_p, jlongArray, jmethodID, jobject, jobjectArray, 9 | jshort, jshort_p, jshortArray, jsize, jstring, 10 | ) 11 | 12 | # If RUBICON_LIBRARY is set in the environment, rely on it. If not, 13 | # import and use ctypes.util to find it. We defer the ctypes.util 14 | # import for speed, since on Android (a performance-limited target), 15 | # avoiding an import can be a big win. 16 | _env_java_lib = os.environ.get('RUBICON_LIBRARY') 17 | if _env_java_lib: 18 | java = cdll.LoadLibrary(_env_java_lib) 19 | else: 20 | from ctypes import util 21 | _java_lib = util.find_library('rubicon') 22 | if _java_lib is None: 23 | raise ValueError("Can't find Rubicon library") 24 | java = cdll.LoadLibrary(_java_lib) 25 | 26 | # These are the parts of the JNI API we use. You can find the spec for the rest here: 27 | # https://docs.oracle.com/javase/8/docs/technotes/guides/jni/spec/functions.html 28 | # 29 | # The JNI API has been stable for many years, so we don't bother introspecting which 30 | # version of Java we are running against. If an incompatible version arises one day, 31 | # we may need to. 32 | 33 | java.FindClass.restype = jclass 34 | java.FindClass.argtypes = [c_char_p] 35 | 36 | java.NewGlobalRef.restype = jobject 37 | java.NewGlobalRef.argtypes = [jobject] 38 | 39 | java.DeleteLocalRef.restype = None 40 | java.DeleteLocalRef.argtypes = [jobject] 41 | 42 | java.NewLocalRef.restype = jobject 43 | java.NewLocalRef.argtypes = [jobject] 44 | 45 | java.NewObject.restype = jobject 46 | java.NewObject.argtypes = [jclass, jmethodID] 47 | 48 | java.GetMethodID.restype = jmethodID 49 | java.GetMethodID.argtypes = [jclass, c_char_p, c_char_p] 50 | 51 | java.CallObjectMethod.restype = jobject 52 | java.CallObjectMethod.argtypes = [jobject, jmethodID] 53 | java.CallBooleanMethod.restype = jboolean 54 | java.CallBooleanMethod.argtypes = [jobject, jmethodID] 55 | java.CallByteMethod.restype = jbyte 56 | java.CallByteMethod.argtypes = [jobject, jmethodID] 57 | java.CallCharMethod.restype = jchar 58 | java.CallCharMethod.argtypes = [jobject, jmethodID] 59 | java.CallShortMethod.restype = jshort 60 | java.CallShortMethod.argtypes = [jobject, jmethodID] 61 | java.CallIntMethod.restype = jint 62 | java.CallIntMethod.argtypes = [jobject, jmethodID] 63 | java.CallLongMethod.restype = jlong 64 | java.CallLongMethod.argtypes = [jobject, jmethodID] 65 | java.CallFloatMethod.restype = jfloat 66 | java.CallFloatMethod.argtypes = [jobject, jmethodID] 67 | java.CallDoubleMethod.restype = jdouble 68 | java.CallDoubleMethod.argtypes = [jobject, jmethodID] 69 | java.CallVoidMethod.restype = None 70 | java.CallVoidMethod.argtypes = [jobject, jmethodID] 71 | 72 | java.GetFieldID.restype = jfieldID 73 | java.GetFieldID.argtypes = [jclass, c_char_p, c_char_p] 74 | 75 | java.GetObjectField.restype = jobject 76 | java.GetObjectField.argtypes = [jobject, jfieldID] 77 | java.GetBooleanField.restype = jboolean 78 | java.GetBooleanField.argtypes = [jobject, jfieldID] 79 | java.GetByteField.restype = jbyte 80 | java.GetByteField.argtypes = [jobject, jfieldID] 81 | java.GetCharField.restype = jchar 82 | java.GetCharField.argtypes = [jobject, jfieldID] 83 | java.GetShortField.restype = jshort 84 | java.GetShortField.argtypes = [jobject, jfieldID] 85 | java.GetIntField.restype = jint 86 | java.GetIntField.argtypes = [jobject, jfieldID] 87 | java.GetLongField.restype = jlong 88 | java.GetLongField.argtypes = [jobject, jfieldID] 89 | java.GetFloatField.restype = jfloat 90 | java.GetFloatField.argtypes = [jobject, jfieldID] 91 | java.GetDoubleField.restype = jdouble 92 | java.GetDoubleField.argtypes = [jobject, jfieldID] 93 | 94 | java.SetObjectField.restype = None 95 | java.SetObjectField.argtypes = [jobject, jfieldID, jobject] 96 | java.SetBooleanField.restype = None 97 | java.SetBooleanField.argtypes = [jobject, jfieldID, jboolean] 98 | java.SetByteField.restype = None 99 | java.SetByteField.argtypes = [jobject, jfieldID, jbyte] 100 | java.SetCharField.restype = None 101 | java.SetCharField.argtypes = [jobject, jfieldID, jchar] 102 | java.SetShortField.restype = None 103 | java.SetShortField.argtypes = [jobject, jfieldID, jshort] 104 | java.SetIntField.restype = None 105 | java.SetIntField.argtypes = [jobject, jfieldID, jint] 106 | java.SetLongField.restype = None 107 | java.SetLongField.argtypes = [jobject, jfieldID, jlong] 108 | java.SetFloatField.restype = None 109 | java.SetFloatField.argtypes = [jobject, jfieldID, jfloat] 110 | java.SetDoubleField.restype = None 111 | java.SetDoubleField.argtypes = [jobject, jfieldID, jdouble] 112 | 113 | java.GetStaticMethodID.restype = jmethodID 114 | java.GetStaticMethodID.argtypes = [jclass, c_char_p, c_char_p] 115 | 116 | java.CallStaticObjectMethod.restype = jobject 117 | java.CallStaticObjectMethod.argtypes = [jclass, jmethodID] 118 | java.CallStaticBooleanMethod.restype = jboolean 119 | java.CallStaticBooleanMethod.argtypes = [jclass, jmethodID] 120 | java.CallStaticByteMethod.restype = jbyte 121 | java.CallStaticByteMethod.argtypes = [jclass, jmethodID] 122 | java.CallStaticCharMethod.restype = jchar 123 | java.CallStaticCharMethod.argtypes = [jclass, jmethodID] 124 | java.CallStaticShortMethod.restype = jshort 125 | java.CallStaticShortMethod.argtypes = [jclass, jmethodID] 126 | java.CallStaticIntMethod.restype = jint 127 | java.CallStaticIntMethod.argtypes = [jclass, jmethodID] 128 | java.CallStaticLongMethod.restype = jlong 129 | java.CallStaticLongMethod.argtypes = [jclass, jmethodID] 130 | java.CallStaticFloatMethod.restype = jfloat 131 | java.CallStaticFloatMethod.argtypes = [jclass, jmethodID] 132 | java.CallStaticDoubleMethod.restype = jdouble 133 | java.CallStaticDoubleMethod.argtypes = [jclass, jmethodID] 134 | java.CallStaticVoidMethod.restype = None 135 | java.CallStaticVoidMethod.argtypes = [jclass, jmethodID] 136 | 137 | java.GetStaticFieldID.restype = jfieldID 138 | java.GetStaticFieldID.argtypes = [jclass, c_char_p, c_char_p] 139 | 140 | java.GetStaticObjectField.restype = jobject 141 | java.GetStaticObjectField.argtypes = [jclass, jfieldID] 142 | java.GetStaticBooleanField.restype = jboolean 143 | java.GetStaticBooleanField.argtypes = [jclass, jfieldID] 144 | java.GetStaticByteField.restype = jbyte 145 | java.GetStaticByteField.argtypes = [jclass, jfieldID] 146 | java.GetStaticCharField.restype = jchar 147 | java.GetStaticCharField.argtypes = [jclass, jfieldID] 148 | java.GetStaticShortField.restype = jshort 149 | java.GetStaticShortField.argtypes = [jclass, jfieldID] 150 | java.GetStaticIntField.restype = jint 151 | java.GetStaticIntField.argtypes = [jclass, jfieldID] 152 | java.GetStaticLongField.restype = jlong 153 | java.GetStaticLongField.argtypes = [jclass, jfieldID] 154 | java.GetStaticFloatField.restype = jfloat 155 | java.GetStaticFloatField.argtypes = [jclass, jfieldID] 156 | java.GetStaticDoubleField.restype = jdouble 157 | java.GetStaticDoubleField.argtypes = [jclass, jfieldID] 158 | 159 | java.NewStringUTF.restype = jstring 160 | java.NewStringUTF.argtypes = [c_char_p] 161 | java.GetStringUTFChars.restype = c_char_p 162 | java.GetStringUTFChars.argtypes = [jstring, jboolean_p] 163 | 164 | java.GetArrayLength.restype = jsize 165 | java.GetArrayLength.argtypes = [jarray] 166 | 167 | java.NewObjectArray.restype = jobjectArray 168 | java.NewObjectArray.argtypes = [jsize, jclass, jobject] 169 | java.GetObjectArrayElement.restype = jobject 170 | java.GetObjectArrayElement.argtypes = [jobjectArray, jsize] 171 | java.SetObjectArrayElement.restype = None 172 | java.SetObjectArrayElement.argtypes = [jobjectArray, jsize, jobject] 173 | 174 | java.NewByteArray.restype = jbyteArray 175 | java.NewByteArray.argtypes = [jsize] 176 | java.SetByteArrayRegion.restype = None 177 | java.SetByteArrayRegion.argtypes = [jbyteArray, jsize, jsize, jbyte_p] 178 | java.GetByteArrayElements.restype = jbyte_p 179 | java.GetByteArrayElements.argtypes = [jbyteArray, jboolean_p] 180 | 181 | java.NewBooleanArray.restype = jbooleanArray 182 | java.NewBooleanArray.argtypes = [jsize] 183 | java.SetBooleanArrayRegion.restype = None 184 | java.SetBooleanArrayRegion.argtypes = [jbooleanArray, jsize, jsize, jboolean_p] 185 | java.GetBooleanArrayElements.restype = jboolean_p 186 | java.GetBooleanArrayElements.argtypes = [jbooleanArray, jboolean_p] 187 | 188 | java.NewDoubleArray.restype = jdoubleArray 189 | java.NewDoubleArray.argtypes = [jsize] 190 | java.SetDoubleArrayRegion.restype = None 191 | java.SetDoubleArrayRegion.argtypes = [jdoubleArray, jsize, jsize, jdouble_p] 192 | java.GetDoubleArrayElements.restype = jdouble_p 193 | java.GetDoubleArrayElements.argtypes = [jdoubleArray, jboolean_p] 194 | 195 | java.NewShortArray.restype = jshortArray 196 | java.NewShortArray.argtypes = [jsize] 197 | java.SetShortArrayRegion.restype = None 198 | java.SetShortArrayRegion.argtypes = [jshortArray, jsize, jsize, jshort_p] 199 | java.GetShortArrayElements.restype = jshort_p 200 | java.GetShortArrayElements.argtypes = [jshortArray, jboolean_p] 201 | 202 | java.NewIntArray.restype = jintArray 203 | java.NewIntArray.argtypes = [jsize] 204 | java.SetIntArrayRegion.restype = None 205 | java.SetIntArrayRegion.argtypes = [jintArray, jsize, jsize, jint_p] 206 | java.GetIntArrayElements.restype = jint_p 207 | java.GetIntArrayElements.argtypes = [jintArray, jboolean_p] 208 | 209 | java.NewLongArray.restype = jlongArray 210 | java.NewLongArray.argtypes = [jsize] 211 | java.SetLongArrayRegion.restype = None 212 | java.SetLongArrayRegion.argtypes = [jlongArray, jsize, jsize, jlong_p] 213 | java.GetLongArrayElements.restype = jlong_p 214 | java.GetLongArrayElements.argtypes = [jlongArray, jboolean_p] 215 | 216 | java.NewFloatArray.restype = jfloatArray 217 | java.NewFloatArray.argtypes = [jsize] 218 | java.SetFloatArrayRegion.restype = None 219 | java.SetFloatArrayRegion.argtypes = [jfloatArray, jsize, jsize, jfloat_p] 220 | java.GetFloatArrayElements.restype = jfloat_p 221 | java.GetFloatArrayElements.argtypes = [jfloatArray, jboolean_p] 222 | 223 | 224 | class _ReflectionAPI(object): 225 | "A lazy-loading proxy for the key classes and methods in the Java reflection API" 226 | def __init__(self): 227 | self._attrs = {} 228 | self._descriptors = { 229 | 'Class': ('FindClass', b'java/lang/Class'), 230 | 'Class__getName': ('GetMethodID', 'Class', b'getName', b'()Ljava/lang/String;'), 231 | 'Class__getConstructors': ( 232 | 'GetMethodID', 'Class', b'getConstructors', b'()[Ljava/lang/reflect/Constructor;' 233 | ), 234 | 'Class__getMethods': ('GetMethodID', 'Class', b'getMethods', b'()[Ljava/lang/reflect/Method;'), 235 | 'Class__getInterfaces': ('GetMethodID', 'Class', b'getInterfaces', b'()[Ljava/lang/Class;'), 236 | 'Class__getSuperclass': ('GetMethodID', 'Class', b'getSuperclass', b'()Ljava/lang/Class;'), 237 | 238 | 'Constructor': ('FindClass', b'java/lang/reflect/Constructor'), 239 | 'Constructor__getParameterTypes': ( 240 | 'GetMethodID', 'Constructor', b'getParameterTypes', b'()[Ljava/lang/Class;' 241 | ), 242 | 'Constructor__getModifiers': ('GetMethodID', 'Constructor', b'getModifiers', b'()I'), 243 | 244 | 'Method': ('FindClass', b'java/lang/reflect/Method'), 245 | 'Method__getName': ('GetMethodID', 'Method', b'getName', b'()Ljava/lang/String;'), 246 | 'Method__getReturnType': ( 247 | 'GetMethodID', 'Method', b'getReturnType', b'()Ljava/lang/Class;' 248 | ), 249 | 'Method__getParameterTypes': ( 250 | 'GetMethodID', 'Method', b'getParameterTypes', b'()[Ljava/lang/Class;' 251 | ), 252 | 'Method__getModifiers': ('GetMethodID', 'Method', b'getModifiers', b'()I'), 253 | 254 | 'Field': ('FindClass', b'java/lang/reflect/Field'), 255 | 'Field__getType': ('GetMethodID', 'Field', b'getType', b'()Ljava/lang/Class;'), 256 | 257 | 'Modifier': ('FindClass', b'java/lang/reflect/Modifier'), 258 | 'Modifier__isStatic': ('GetStaticMethodID', 'Modifier', b'isStatic', b'(I)Z'), 259 | 'Modifier__isPublic': ('GetStaticMethodID', 'Modifier', b'isPublic', b'(I)Z'), 260 | 261 | 'Python': ('FindClass', b'org/beeware/rubicon/Python'), 262 | 'Python__proxy': ('GetStaticMethodID', 'Python', b'proxy', b'(Ljava/lang/Class;J)Ljava/lang/Object;'), 263 | 'Python__getField': ( 264 | 'GetStaticMethodID', 'Python', b'getField', 265 | b'(Ljava/lang/Class;Ljava/lang/String;Z)Ljava/lang/reflect/Field;' 266 | ), 267 | 'Python__getMethods': ( 268 | 'GetStaticMethodID', 'Python', b'getMethods', 269 | b'(Ljava/lang/Class;Ljava/lang/String;Z)[Ljava/lang/reflect/Method;' 270 | ), 271 | 272 | 'Boolean': ('FindClass', b'java/lang/Boolean'), 273 | 'Boolean__booleanValue': ('GetMethodID', 'Boolean', b'booleanValue', b'()Z'), 274 | 275 | 'Byte': ('FindClass', b'java/lang/Byte'), 276 | 'Byte__byteValue': ('GetMethodID', 'Byte', b'byteValue', b'()B'), 277 | 278 | 'Char': ('FindClass', b'java/lang/Char'), 279 | 'Char__charValue': ('GetMethodID', 'Char', b'charValue', b'()C'), 280 | 281 | 'Short': ('FindClass', b'java/lang/Short'), 282 | 'Short__shortValue': ('GetMethodID', 'Short', b'shortValue', b'()S'), 283 | 284 | 'Integer': ('FindClass', b'java/lang/Integer'), 285 | 'Integer__intValue': ('GetMethodID', 'Integer', b'intValue', b'()I'), 286 | 287 | 'Long': ('FindClass', b'java/lang/Long'), 288 | 'Long__longValue': ('GetMethodID', 'Long', b'longValue', b'()J'), 289 | 290 | 'Float': ('FindClass', b'java/lang/Float'), 291 | 'Float__floatValue': ('GetMethodID', 'Float', b'floatValue', b'()F'), 292 | 293 | 'Double': ('FindClass', b'java/lang/Double'), 294 | 'Double__doubleValue': ('GetMethodID', 'Double', b'doubleValue', b'()D'), 295 | } 296 | 297 | def __getattr__(self, name): 298 | try: 299 | result = self._attrs[name] 300 | return result 301 | except KeyError: 302 | try: 303 | args = self._descriptors[name] 304 | if args[0] == 'FindClass': 305 | result = java.FindClass(*args[1:]) 306 | if result.value is None: 307 | raise RuntimeError("Couldn't find Java class '%s'" % args[1]) 308 | 309 | result = cast(java.NewGlobalRef(result), jclass) 310 | 311 | elif args[0] == 'GetMethodID': 312 | klass = getattr(self, args[1]) 313 | result = java.GetMethodID(klass, *args[2:]) 314 | if result.value is None: 315 | raise RuntimeError("Couldn't find Java method '%s.%s'" % (args[1], args[2])) 316 | 317 | elif args[0] == 'GetStaticMethodID': 318 | klass = getattr(self, args[1]) 319 | result = java.GetStaticMethodID(klass, *args[2:]) 320 | if result.value is None: 321 | raise RuntimeError("Couldn't find Java static method '%s.%s'" % (args[1], args[2])) 322 | 323 | self._attrs[name] = result 324 | return result 325 | except KeyError: 326 | raise RuntimeError("Unexpected reflection API request '%s'" % name) 327 | 328 | 329 | reflect = _ReflectionAPI() 330 | -------------------------------------------------------------------------------- /rubicon/java/types.py: -------------------------------------------------------------------------------- 1 | from ctypes import ( 2 | POINTER, Structure, c_bool, c_byte, c_char_p, c_double, c_float, c_int16, 3 | c_int32, c_int64, c_void_p, c_wchar, 4 | ) 5 | 6 | __all__ = [ 7 | 'jboolean', 'jbyte', 'jchar', 'jshort', 'jint', 'jlong', 'jfloat', 'jdouble', 8 | 'jboolean_p', 'jbyte_p', 'jchar_p', 'jshort_p', 'jint_p', 'jlong_p', 'jfloat_p', 'jdouble_p', 9 | 'jsize', 10 | 'jobject', 'jmethodID', 'jfieldID', 11 | 'jclass', 'jthrowable', 'jstring', 'jarray', 12 | 'jbooleanArray', 'jbyteArray', 'jcharArray', 'jshortArray', 'jintArray', 13 | 'jlongArray', 'jfloatArray', 'jdoubleArray', 'jobjectArray', 14 | 'jweak', 'JNINativeMethod', 'JNINativeMethod_p', 15 | 'JavaVM', 'JavaVM_p', 'JNIEnv', 16 | ] 17 | 18 | jboolean = c_bool 19 | jbyte = c_byte 20 | jchar = c_wchar 21 | jshort = c_int16 22 | jint = c_int32 23 | jlong = c_int64 24 | jfloat = c_float 25 | jdouble = c_double 26 | 27 | jboolean_p = POINTER(jboolean) 28 | jbyte_p = POINTER(jbyte) 29 | jchar_p = POINTER(jchar) 30 | jshort_p = POINTER(jshort) 31 | jint_p = POINTER(jint) 32 | jlong_p = POINTER(jlong) 33 | jfloat_p = POINTER(jfloat) 34 | jdouble_p = POINTER(jdouble) 35 | 36 | jsize = jint 37 | 38 | 39 | class jobject(c_void_p): 40 | pass 41 | 42 | 43 | class jmethodID(jobject): 44 | pass 45 | 46 | 47 | class jfieldID(jobject): 48 | pass 49 | 50 | 51 | class jclass(jobject): 52 | pass 53 | 54 | 55 | class jthrowable(jobject): 56 | pass 57 | 58 | 59 | class jstring(jobject): 60 | pass 61 | 62 | 63 | class jarray(jobject): 64 | pass 65 | 66 | 67 | class jbooleanArray(jarray): 68 | pass 69 | 70 | 71 | class jbyteArray(jarray): 72 | pass 73 | 74 | 75 | class jcharArray(jarray): 76 | pass 77 | 78 | 79 | class jshortArray(jarray): 80 | pass 81 | 82 | 83 | class jintArray(jarray): 84 | pass 85 | 86 | 87 | class jlongArray(jarray): 88 | pass 89 | 90 | 91 | class jfloatArray(jarray): 92 | pass 93 | 94 | 95 | class jdoubleArray(jarray): 96 | pass 97 | 98 | 99 | class jobjectArray(jarray): 100 | pass 101 | 102 | 103 | class jweak(jobject): 104 | pass 105 | 106 | 107 | class JNINativeMethod(Structure): 108 | _fields_ = [ 109 | ("name", c_char_p), 110 | ("signature", c_char_p), 111 | ("fnPtr", c_void_p), 112 | ] 113 | 114 | 115 | JNINativeMethod_p = POINTER(JNINativeMethod) 116 | 117 | 118 | class JavaVM(c_void_p): 119 | pass 120 | 121 | 122 | JavaVM_p = POINTER(JavaVM) 123 | 124 | 125 | class JNIEnv(c_void_p): 126 | pass 127 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [metadata] 2 | name = rubicon-java 3 | url = https://beeware.org/rubicon 4 | project_urls = 5 | Funding = https://beeware.org/contributing/membership/ 6 | Documentation = https://rubicon-java.readthedocs.io/en/latest/ 7 | Tracker = https://github.com/beeware/rubicon-java/issues 8 | Source = https://github.com/beeware/rubicon-java 9 | author = Russell Keith-Magee 10 | author_email = russell@keith-magee.com 11 | classifiers = 12 | Development Status :: 4 - Beta 13 | Intended Audience :: Developers 14 | License :: OSI Approved :: BSD License 15 | Programming Language :: Java 16 | Programming Language :: Python :: 3 17 | Programming Language :: Python :: 3.6 18 | Programming Language :: Python :: 3.7 19 | Programming Language :: Python :: 3.8 20 | Programming Language :: Python :: 3.9 21 | Programming Language :: Python :: 3.10 22 | Programming Language :: Python :: 3 :: Only 23 | Topic :: Software Development 24 | license = New BSD 25 | license_files = 26 | LICENSE 27 | description = A bridge between the Java Runtime Environment and Python. 28 | long_description = file: README.rst 29 | long_description_content_type = text/x-rst 30 | 31 | [options] 32 | python_requires = >=3.6 33 | packages = rubicon.java 34 | namespace_packages = 35 | rubicon 36 | 37 | [flake8] 38 | # https://flake8.readthedocs.org/en/latest/ 39 | exclude= 40 | *.egg-info/* 41 | .git/* 42 | .tox/* 43 | __pycache__ 44 | build/* 45 | docs/* 46 | venv*/* 47 | local/* 48 | max-complexity = 26 49 | max-line-length = 119 50 | 51 | # The following issues are ignored because they do not match our code style: 52 | # E133: closing bracket missing indentation 53 | # E226: missing whitespace around arithmetic operator 54 | # W503: line break occurred before a binary operator 55 | # C901: Cyclomatic complexity is too high 56 | ignore = E133,E226,W503,C901 57 | 58 | [isort] 59 | combine_as_imports = true 60 | include_trailing_comma = true 61 | line_length = 79 62 | multi_line_output = 5 63 | skip = 64 | docs/conf.py 65 | venv 66 | local 67 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import re 3 | 4 | from setuptools import setup 5 | 6 | with open('./rubicon/java/__init__.py', encoding='utf8') as version_file: 7 | version_match = re.search(r"^__version__ = ['\"]([^'\"]*)['\"]", version_file.read(), re.M) 8 | if version_match: 9 | version = version_match.group(1) 10 | else: 11 | raise RuntimeError("Unable to find version string.") 12 | 13 | 14 | setup( 15 | version=version, 16 | ) 17 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/beeware/rubicon-java/2d3842dbf048faf19536cf8a580c338d76943dda/tests/__init__.py -------------------------------------------------------------------------------- /tests/runner.py: -------------------------------------------------------------------------------- 1 | from unittest.main import main 2 | 3 | if __name__ == '__main__': 4 | main(module=None) 5 | -------------------------------------------------------------------------------- /tests/test_jni.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import division, print_function, unicode_literals 3 | 4 | from unittest import TestCase 5 | 6 | from rubicon.java import cast, java, jdouble, jlong, jstring 7 | 8 | 9 | class JNITest(TestCase): 10 | 11 | def test_string(self): 12 | "A Java string can be created, and the content returned" 13 | # This string contains unicode characters 14 | s = "H\xe9llo world" 15 | java_string = java.NewStringUTF(s.encode('utf-8')) 16 | self.assertEqual(java.GetStringUTFChars(java_string, None).decode('utf-8'), s) 17 | 18 | def test_non_existent(self): 19 | "Non-existent classes/methods/fields return None from Find/Get APIs" 20 | # A class that doesn't exist 21 | UnknownClass = java.FindClass(b"java/XXX") 22 | self.assertIsNone(UnknownClass.value) 23 | 24 | # A class that does exist, that we can then search for non-existent methods 25 | Example = java.FindClass(b"org/beeware/rubicon/test/Example") 26 | self.assertIsNotNone(Example.value) 27 | 28 | # Fields and Methods (static and non-static) 29 | self.assertIsNone(java.GetMethodID(Example, b"xxx", b"()V").value) 30 | self.assertIsNone(java.GetStaticMethodID(Example, b"xxx", b"()V").value) 31 | self.assertIsNone(java.GetFieldID(Example, b"xxx", b"I").value) 32 | self.assertIsNone(java.GetStaticFieldID(Example, b"xxx", b"I").value) 33 | 34 | # Bad descriptors for existing fields/methods also fail. 35 | self.assertIsNone(java.GetMethodID(Example, b"get_int_field", b"()D").value) 36 | self.assertIsNone(java.GetStaticMethodID(Example, b"get_static_int_field", b"()D").value) 37 | self.assertIsNone(java.GetFieldID(Example, b"int_field", b"D").value) 38 | self.assertIsNone(java.GetStaticFieldID(Example, b"static_int_field", b"D").value) 39 | 40 | def test_object_lifecycle(self): 41 | "The basic lifecycle operations of an object can be performed" 42 | # Get a reference to the org.beeware.test.Example class 43 | Example = java.FindClass(b"org/beeware/rubicon/test/Example") 44 | self.assertIsNotNone(Example.value) 45 | 46 | # Find the default constructor 47 | Example__init = java.GetMethodID(Example, b"", b"()V") 48 | self.assertIsNotNone(Example__init.value) 49 | 50 | # Find the 'one int' constructor 51 | Example__init_i = java.GetMethodID(Example, b"", b"(I)V") 52 | self.assertIsNotNone(Example__init_i.value) 53 | 54 | # Find the 'two int' constructor 55 | Example__init_ii = java.GetMethodID(Example, b"", b"(II)V") 56 | self.assertIsNotNone(Example__init_ii.value) 57 | 58 | # Find the BaseExample.set_base_int_field() method on Example 59 | Example__set_base_int_field = java.GetMethodID(Example, b"set_base_int_field", b"(I)V") 60 | self.assertIsNotNone(Example__set_base_int_field.value) 61 | 62 | # Find the Example.get_base_int_field() method on Example 63 | Example__get_base_int_field = java.GetMethodID(Example, b"get_base_int_field", b"()I") 64 | self.assertIsNotNone(Example__get_base_int_field.value) 65 | 66 | # Find the Example.set_int_field() method 67 | Example__set_int_field = java.GetMethodID(Example, b"set_int_field", b"(I)V") 68 | self.assertIsNotNone(Example__set_int_field.value) 69 | 70 | # Find the Example.get_int_field() method 71 | Example__get_int_field = java.GetMethodID(Example, b"get_int_field", b"()I") 72 | self.assertIsNotNone(Example__get_int_field.value) 73 | 74 | # Find Example.int_field 75 | Example__int_field = java.GetFieldID(Example, b"int_field", b"I") 76 | self.assertIsNotNone(Example__int_field.value) 77 | 78 | # Find Example.base_int_field 79 | Example__base_int_field = java.GetFieldID(Example, b"base_int_field", b"I") 80 | self.assertIsNotNone(Example__base_int_field.value) 81 | 82 | # Create an instance of org.beeware.test.Example using the default constructor 83 | obj1 = java.NewObject(Example, Example__init) 84 | self.assertIsNotNone(obj1.value) 85 | 86 | # Use the get_int_field and get_base_int_field methods 87 | self.assertEqual(java.CallIntMethod(obj1, Example__get_base_int_field), 22) 88 | self.assertEqual(java.CallIntMethod(obj1, Example__get_int_field), 33) 89 | 90 | self.assertEqual(java.GetIntField(obj1, Example__base_int_field), 22) 91 | self.assertEqual(java.GetIntField(obj1, Example__int_field), 33) 92 | 93 | # Use the set_int_field and set_base_int_field methods 94 | java.CallVoidMethod(obj1, Example__set_base_int_field, 1137) 95 | java.CallVoidMethod(obj1, Example__set_int_field, 1142) 96 | 97 | # Confirm that the values have changed 98 | self.assertEqual(java.CallIntMethod(obj1, Example__get_base_int_field), 1137) 99 | self.assertEqual(java.CallIntMethod(obj1, Example__get_int_field), 1142) 100 | 101 | self.assertEqual(java.GetIntField(obj1, Example__base_int_field), 1137) 102 | self.assertEqual(java.GetIntField(obj1, Example__int_field), 1142) 103 | 104 | # Create an instance of org.beeware.test.Example using the "one int" constructor 105 | obj2 = java.NewObject(Example, Example__init_i, 2242) 106 | self.assertIsNotNone(obj2) 107 | 108 | # Check that instance values are as expected 109 | self.assertEqual(java.CallIntMethod(obj2, Example__get_base_int_field), 44) 110 | self.assertEqual(java.CallIntMethod(obj2, Example__get_int_field), 2242) 111 | 112 | self.assertEqual(java.GetIntField(obj2, Example__base_int_field), 44) 113 | self.assertEqual(java.GetIntField(obj2, Example__int_field), 2242) 114 | 115 | # Create an instance of org.beeware.test.Example using the "two int" constructor 116 | obj3 = java.NewObject(Example, Example__init_ii, 3342, 3337) 117 | self.assertIsNotNone(obj3) 118 | 119 | # Check that instance values are as expected 120 | self.assertEqual(java.CallIntMethod(obj3, Example__get_base_int_field), 3342) 121 | self.assertEqual(java.CallIntMethod(obj3, Example__get_int_field), 3337) 122 | 123 | self.assertEqual(java.GetIntField(obj3, Example__base_int_field), 3342) 124 | self.assertEqual(java.GetIntField(obj3, Example__int_field), 3337) 125 | 126 | def test_static(self): 127 | "Static methods and fields can be invoked" 128 | # Get a reference to the org.beeware.test.Example class 129 | Example = java.FindClass(b"org/beeware/rubicon/test/Example") 130 | self.assertIsNotNone(Example.value) 131 | 132 | # Find the BaseExample.set_static_base_int_field() method on Example 133 | Example__set_static_base_int_field = java.GetStaticMethodID(Example, b"set_static_base_int_field", b"(I)V") 134 | self.assertIsNotNone(Example__set_static_base_int_field.value) 135 | 136 | # Find the Example.get_static_base_int_field() method on Example 137 | Example__get_static_base_int_field = java.GetStaticMethodID(Example, b"get_static_base_int_field", b"()I") 138 | self.assertIsNotNone(Example__get_static_base_int_field.value) 139 | 140 | # Find the Example.set_static_int_field() method 141 | Example__set_static_int_field = java.GetStaticMethodID(Example, b"set_static_int_field", b"(I)V") 142 | self.assertIsNotNone(Example__set_static_int_field.value) 143 | 144 | # Find the Example.get_static_int_field() method 145 | Example__get_static_int_field = java.GetStaticMethodID(Example, b"get_static_int_field", b"()I") 146 | self.assertIsNotNone(Example__get_static_int_field.value) 147 | 148 | # Find Example.static_int_field 149 | Example__static_int_field = java.GetStaticFieldID(Example, b"static_int_field", b"I") 150 | self.assertIsNotNone(Example__static_int_field.value) 151 | 152 | # Find Example.static_base_int_field 153 | Example__static_base_int_field = java.GetStaticFieldID(Example, b"static_base_int_field", b"I") 154 | self.assertIsNotNone(Example__static_base_int_field.value) 155 | 156 | # Use the get_static_int_field and get_static_base_int_field methods 157 | self.assertEqual(java.CallStaticIntMethod(Example, Example__get_static_base_int_field), 1) 158 | self.assertEqual(java.CallStaticIntMethod(Example, Example__get_static_int_field), 11) 159 | 160 | self.assertEqual(java.GetStaticIntField(Example, Example__static_base_int_field), 1) 161 | self.assertEqual(java.GetStaticIntField(Example, Example__static_int_field), 11) 162 | 163 | # Use the set_static_int_field and set_static_base_int_field methods 164 | java.CallVoidMethod(Example, Example__set_static_base_int_field, 1137) 165 | java.CallVoidMethod(Example, Example__set_static_int_field, 1142) 166 | 167 | # Confirm that the values have changed 168 | self.assertEqual(java.CallStaticIntMethod(Example, Example__get_static_base_int_field), 1137) 169 | self.assertEqual(java.CallStaticIntMethod(Example, Example__get_static_int_field), 1142) 170 | 171 | self.assertEqual(java.GetStaticIntField(Example, Example__static_base_int_field), 1137) 172 | self.assertEqual(java.GetStaticIntField(Example, Example__static_int_field), 1142) 173 | 174 | def test_string_method(self): 175 | "A Java string can be created, and the content returned" 176 | # This string contains unicode characters 177 | s = "Woop" 178 | java_string = java.NewStringUTF(s.encode('utf-8')) 179 | 180 | Example = java.FindClass(b"org/beeware/rubicon/test/Example") 181 | self.assertIsNotNone(Example.value) 182 | 183 | # Find the default constructor 184 | Example__init = java.GetMethodID(Example, b"", b"()V") 185 | self.assertIsNotNone(Example__init.value) 186 | 187 | # Find the Example.duplicate_string() method on Example 188 | Example__duplicate_string = java.GetMethodID( 189 | Example, b"duplicate_string", b"(Ljava/lang/String;)Ljava/lang/String;" 190 | ) 191 | self.assertIsNotNone(Example__duplicate_string.value) 192 | 193 | # Create an instance of org.beeware.test.Example using the default constructor 194 | obj1 = java.NewObject(Example, Example__init) 195 | self.assertIsNotNone(obj1.value) 196 | 197 | # Invoke the string duplication method 198 | result = java.CallObjectMethod(obj1, Example__duplicate_string, java_string) 199 | self.assertEqual(java.GetStringUTFChars(cast(result, jstring), None).decode('utf-8'), "WoopWoop") 200 | 201 | def test_float_method(self): 202 | "A Java float can be created, and the content returned" 203 | # This string contains unicode characters 204 | Example = java.FindClass(b"org/beeware/rubicon/test/Example") 205 | self.assertIsNotNone(Example.value) 206 | 207 | # Find the default constructor 208 | Example__init = java.GetMethodID(Example, b"", b"()V") 209 | self.assertIsNotNone(Example__init.value) 210 | 211 | # Find the Example.area_of_square() method on Example 212 | Example__area_of_square = java.GetMethodID(Example, b"area_of_square", b"(F)F") 213 | self.assertIsNotNone(Example__area_of_square.value) 214 | 215 | # Create an instance of org.beeware.test.Example using the default constructor 216 | obj1 = java.NewObject(Example, Example__init) 217 | self.assertIsNotNone(obj1.value) 218 | 219 | # Invoke the area method 220 | result = java.CallFloatMethod(obj1, Example__area_of_square, jdouble(1.5)) 221 | self.assertEqual(result, 2.25) 222 | 223 | def test_jlong(self): 224 | """"A jlong can be created, and the content returned, even for large/small values 225 | that push the boundaries of signed 64-bit integers""" 226 | # Get the getter and setter 227 | Example = java.FindClass(b"org/beeware/rubicon/test/Example") 228 | getter = java.GetStaticMethodID(Example, b"get_static_long_field", b"()J") 229 | self.assertIsNotNone(getter.value) 230 | setter = java.GetStaticMethodID(Example, b"set_static_long_field", b"(J)V") 231 | self.assertIsNotNone(setter.value) 232 | 233 | for num in (0, -1, 1 << 31, 1 << 32, -1 * 1 << 32, (1 << 63 - 1), -1 * (1 << 63 - 1)): 234 | # Validate rubicon.types.jlong. 235 | self.assertEqual(jlong(num).value, num) 236 | # Validate round trips through Java. 237 | java.CallStaticVoidMethod(Example, setter, jlong(num)) 238 | self.assertEqual(java.CallStaticLongMethod(Example, getter), num) 239 | -------------------------------------------------------------------------------- /tests/test_rubicon.py: -------------------------------------------------------------------------------- 1 | import math 2 | import sys 3 | from unittest import TestCase 4 | 5 | from rubicon.java import JavaClass, JavaInterface, JavaNull, jdouble, jfloat, jstring, jlong, jshort, jint 6 | 7 | 8 | class JNITest(TestCase): 9 | 10 | def test_simple_object(self): 11 | 12 | Stack = JavaClass('java/util/Stack') 13 | 14 | stack = Stack() 15 | 16 | stack.push("Hello") 17 | stack.push("World") 18 | 19 | # The stack methods are protyped to return java/lang/Object, 20 | # so we need to call toString() to get the actual content... 21 | self.assertEqual(stack.pop().toString(), "World") 22 | self.assertEqual(stack.pop().toString(), "Hello") 23 | 24 | # with self.assertRaises(Exception): 25 | # stack.pop() 26 | 27 | def test_subclass_impossible(self): 28 | def make_subclass(): 29 | Stack = JavaClass('java/util/Stack') 30 | 31 | class ImpossibleStackSubclass(Stack): 32 | pass 33 | 34 | self.assertRaises(NotImplementedError, make_subclass) 35 | 36 | def test_field(self): 37 | "A field on an instance can be accessed and mutated" 38 | Example = JavaClass('org/beeware/rubicon/test/Example') 39 | 40 | obj = Example() 41 | 42 | self.assertEqual(obj.base_int_field, 22) 43 | self.assertEqual(obj.int_field, 33) 44 | 45 | obj.base_int_field = 8888 46 | obj.int_field = 9999 47 | 48 | self.assertEqual(obj.base_int_field, 8888) 49 | self.assertEqual(obj.int_field, 9999) 50 | 51 | def test_method(self): 52 | "An instance method can be invoked." 53 | Example = JavaClass('org/beeware/rubicon/test/Example') 54 | 55 | obj = Example() 56 | self.assertEqual(obj.get_base_int_field(), 22) 57 | self.assertEqual(obj.get_int_field(), 33) 58 | 59 | obj.set_base_int_field(8888) 60 | obj.set_int_field(9999) 61 | 62 | self.assertEqual(obj.get_base_int_field(), 8888) 63 | self.assertEqual(obj.get_int_field(), 9999) 64 | 65 | def test_static_field(self): 66 | "A static field on a class can be accessed and mutated" 67 | with ExampleClassWithCleanup() as Example: 68 | Example.set_static_base_int_field(1) 69 | Example.set_static_int_field(11) 70 | 71 | self.assertEqual(Example.static_base_int_field, 1) 72 | self.assertEqual(Example.static_int_field, 11) 73 | 74 | Example.static_base_int_field = 1188 75 | Example.static_int_field = 1199 76 | 77 | self.assertEqual(Example.static_base_int_field, 1188) 78 | self.assertEqual(Example.static_int_field, 1199) 79 | 80 | def test_static_method(self): 81 | "A static method on a class can be invoked." 82 | with ExampleClassWithCleanup() as Example: 83 | self.assertEqual(Example.get_static_base_int_field(), 1137) 84 | self.assertEqual(Example.get_static_int_field(), 1142) 85 | 86 | Example.set_static_base_int_field(2288) 87 | Example.set_static_int_field(2299) 88 | 89 | self.assertEqual(Example.get_static_base_int_field(), 2288) 90 | self.assertEqual(Example.get_static_int_field(), 2299) 91 | 92 | def test_non_existent_field(self): 93 | "An attribute error is raised if you invoke a non-existent field." 94 | Example = JavaClass('org/beeware/rubicon/test/Example') 95 | 96 | obj1 = Example() 97 | 98 | # Non-existent fields raise an error. 99 | with self.assertRaises(AttributeError): 100 | obj1.field_doesnt_exist 101 | 102 | # Cache warming doesn't affect anything. 103 | with self.assertRaises(AttributeError): 104 | obj1.field_doesnt_exist 105 | 106 | def test_non_existent_method(self): 107 | "An attribute error is raised if you invoke a non-existent method." 108 | Example = JavaClass('org/beeware/rubicon/test/Example') 109 | 110 | obj1 = Example() 111 | 112 | # Non-existent methods raise an error. 113 | with self.assertRaises(AttributeError): 114 | obj1.method_doesnt_exist() 115 | 116 | # Cache warming doesn't affect anything. 117 | with self.assertRaises(AttributeError): 118 | obj1.method_doesnt_exist() 119 | 120 | def test_non_existent_static_field(self): 121 | "An attribute error is raised if you invoke a non-existent static field." 122 | Example = JavaClass('org/beeware/rubicon/test/Example') 123 | 124 | # Non-existent fields raise an error. 125 | with self.assertRaises(AttributeError): 126 | Example.static_field_doesnt_exist 127 | 128 | # Cache warming doesn't affect anything. 129 | with self.assertRaises(AttributeError): 130 | Example.static_field_doesnt_exist 131 | 132 | def test_non_existent_static_method(self): 133 | "An attribute error is raised if you invoke a non-existent static method." 134 | Example = JavaClass('org/beeware/rubicon/test/Example') 135 | 136 | # Non-existent methods raise an error. 137 | with self.assertRaises(AttributeError): 138 | Example.static_method_doesnt_exist() 139 | 140 | # Cache warming doesn't affect anything. 141 | with self.assertRaises(AttributeError): 142 | Example.static_method_doesnt_exist() 143 | 144 | def test_protected_field(self): 145 | "An attribute error is raised if you invoke a non-public field." 146 | Example = JavaClass('org/beeware/rubicon/test/Example') 147 | 148 | obj1 = Example() 149 | 150 | # Non-public fields raise an error. 151 | with self.assertRaises(AttributeError): 152 | obj1.invisible_field 153 | 154 | # Cache warming doesn't affect anything. 155 | with self.assertRaises(AttributeError): 156 | obj1.invisible_field 157 | 158 | def test_protected_method(self): 159 | "An attribute error is raised if you invoke a non-public method." 160 | Example = JavaClass('org/beeware/rubicon/test/Example') 161 | 162 | obj1 = Example() 163 | 164 | # Non-public methods raise an error. 165 | with self.assertRaises(AttributeError): 166 | obj1.invisible_method() 167 | 168 | # Cache warming doesn't affect anything. 169 | with self.assertRaises(AttributeError): 170 | obj1.invisible_method() 171 | 172 | def test_protected_static_field(self): 173 | "An attribute error is raised if you invoke a non-public static field." 174 | Example = JavaClass('org/beeware/rubicon/test/Example') 175 | 176 | # Non-public fields raise an error. 177 | with self.assertRaises(AttributeError): 178 | Example.static_invisible_field 179 | 180 | # Cache warming doesn't affect anything. 181 | with self.assertRaises(AttributeError): 182 | Example.static_invisible_field 183 | 184 | def test_protected_static_method(self): 185 | "An attribute error is raised if you invoke a non-public static method." 186 | Example = JavaClass('org/beeware/rubicon/test/Example') 187 | 188 | # Non-public methods raise an error. 189 | with self.assertRaises(AttributeError): 190 | Example.static_invisible_method() 191 | 192 | # Cache warming doesn't affect anything. 193 | with self.assertRaises(AttributeError): 194 | Example.static_invisible_method() 195 | 196 | def test_polymorphic_constructor(self): 197 | "Check that the right constructor is activated based on arguments used" 198 | Example = JavaClass('org/beeware/rubicon/test/Example') 199 | 200 | obj1 = Example() 201 | obj2 = Example(2242) 202 | obj3 = Example(3342, 3337) 203 | 204 | self.assertEqual(obj1.base_int_field, 22) 205 | self.assertEqual(obj1.int_field, 33) 206 | 207 | self.assertEqual(obj2.base_int_field, 44) 208 | self.assertEqual(obj2.int_field, 2242) 209 | 210 | self.assertEqual(obj3.base_int_field, 3342) 211 | self.assertEqual(obj3.int_field, 3337) 212 | 213 | # Protected constructors can't be invoked 214 | with self.assertRaises(ValueError): 215 | Example("Hello") 216 | 217 | def test_polymorphic_method(self): 218 | "Check that the right method is activated based on arguments used" 219 | Example = JavaClass('org/beeware/rubicon/test/Example') 220 | obj1 = Example() 221 | 222 | self.assertEqual(obj1.doubler(42), 84) 223 | self.assertEqual(obj1.doubler("wibble"), "wibblewibble") 224 | 225 | # If arguments don't match available options, an error is raised 226 | with self.assertRaises(ValueError): 227 | obj1.doubler(1.234) 228 | 229 | def test_byte_array_arg(self): 230 | "Bytestrings can be used as arguments (as byte arrays)" 231 | Example = JavaClass('org/beeware/rubicon/test/Example') 232 | obj1 = Example() 233 | 234 | self.assertEqual(obj1.doubler(b'abcd'), b'aabbccdd') 235 | 236 | def test_int_array_arg(self): 237 | "Arrays of int can be used as arguments" 238 | Example = JavaClass('org/beeware/rubicon/test/Example') 239 | obj1 = Example() 240 | self.assertEqual(obj1.doubler([1, 2]), [1, 1, 2, 2]) 241 | self.assertEqual(obj1.doubler([jlong(1), jlong(2)]), [1, 1, 2, 2]) 242 | self.assertEqual(obj1.doubler([jshort(1), jshort(2)]), [1, 1, 2, 2]) 243 | self.assertEqual(obj1.doubler([jint(1), jint(2)]), [1, 1, 2, 2]) 244 | 245 | def assertAlmostEqualList(self, actual, expected): 246 | self.assertEqual(len(expected), len(actual), "Lists are different length") 247 | for i, (a, e) in enumerate(zip(actual, expected)): 248 | self.assertAlmostEqual(a, e) 249 | 250 | def test_float_array_arg(self): 251 | "Arrays of float can be used as arguments" 252 | Example = JavaClass('org/beeware/rubicon/test/Example') 253 | obj1 = Example() 254 | 255 | self.assertAlmostEqualList(obj1.doubler([1.1, 2.2]), [1.1, 1.1, 2.2, 2.2]) 256 | self.assertAlmostEqualList(obj1.doubler([jfloat(1.1), jfloat(2.2)]), [1.1, 1.1, 2.2, 2.2]) 257 | self.assertAlmostEqualList(obj1.doubler([jdouble(1.1), jdouble(2.2)]), [1.1, 1.1, 2.2, 2.2]) 258 | 259 | def test_bool_array_arg(self): 260 | "Arrays of bool can be used as arguments" 261 | Example = JavaClass('org/beeware/rubicon/test/Example') 262 | obj1 = Example() 263 | self.assertEqual(obj1.doubler([True, False]), [True, True, False, False]) 264 | 265 | def test_string_array_arg(self): 266 | "Arrays of string can be used as arguments" 267 | Example = JavaClass('org/beeware/rubicon/test/Example') 268 | obj1 = Example() 269 | self.assertEqual(obj1.doubler(["one", "two"]), ["one", "one", "two", "two"]) 270 | 271 | def test_object_array_arg(self): 272 | "Arrays of object can be used as arguments" 273 | Example = JavaClass('org/beeware/rubicon/test/Example') 274 | obj1 = Example() 275 | 276 | Thing = JavaClass('org/beeware/rubicon/test/Thing') 277 | thing1 = Thing('This is one', 1) 278 | thing2 = Thing('This is two', 2) 279 | 280 | self.assertEqual( 281 | [str(obj) for obj in obj1.doubler([thing1, thing2])], 282 | [str(obj) for obj in [thing1, thing1, thing2, thing2]] 283 | ) 284 | 285 | def test_method_null(self): 286 | "Null objects can be passed as arguments" 287 | Example = JavaClass('org/beeware/rubicon/test/Example') 288 | obj1 = Example() 289 | 290 | Thing = JavaClass('org/beeware/rubicon/test/Thing') 291 | thing = Thing('This is thing', 2) 292 | 293 | ICallback = JavaInterface('org/beeware/rubicon/test/ICallback') 294 | 295 | class MyInterface(ICallback): 296 | def poke(self, example, value): 297 | pass 298 | 299 | def peek(self, example, value): 300 | pass 301 | 302 | handler = MyInterface() 303 | 304 | # An explicit typed NULL can be used to match None arguments. 305 | self.assertEqual( 306 | obj1.combiner(3, JavaNull(b'Ljava/lang/String;'), thing, handler, [1, 2]), 307 | "3::::This is thing 2::There is a callback::There are 2 values" 308 | ) 309 | 310 | # A typed Null can be constructed from a primitive Python 311 | self.assertEqual( 312 | obj1.combiner(3, JavaNull(str), thing, handler, [1, 2]), 313 | "3::::This is thing 2::There is a callback::There are 2 values" 314 | ) 315 | 316 | # Every JavaClass has a built-in NULL 317 | self.assertEqual( 318 | obj1.combiner(3, "Pork", Thing.__null__, handler, [1, 2]), 319 | '3::Pork::::There is a callback::There are 2 values' 320 | ) 321 | 322 | # JavaClasses can also be used to construct a null. 323 | self.assertEqual( 324 | obj1.combiner(3, "Pork", JavaNull(Thing), handler, [1, 2]), 325 | '3::Pork::::There is a callback::There are 2 values' 326 | ) 327 | 328 | # Every JavaInterface has a built-in NULL 329 | self.assertEqual( 330 | obj1.combiner(3, "Pork", thing, ICallback.__null__, [1, 2]), 331 | '3::Pork::This is thing 2::::There are 2 values' 332 | ) 333 | 334 | # JavaInterfaces can also be used to construct a null. 335 | self.assertEqual( 336 | obj1.combiner(3, "Pork", thing, JavaNull(ICallback), [1, 2]), 337 | '3::Pork::This is thing 2::::There are 2 values' 338 | ) 339 | 340 | # Arrays are constructed by passing in a list with a single type item. 341 | self.assertEqual( 342 | obj1.combiner(3, "Pork", thing, handler, JavaNull([int])), 343 | '3::Pork::This is thing 2::There is a callback::' 344 | ) 345 | 346 | # If NULL arguments don't match available options, an error is raised 347 | with self.assertRaises(ValueError): 348 | obj1.combiner(3, "Pork", JavaNull(str), handler, [1, 2]), 349 | 350 | def test_polymorphic_method_null(self): 351 | "Polymorphic methods can be passed NULLs" 352 | Example = JavaClass('org/beeware/rubicon/test/Example') 353 | obj1 = Example() 354 | 355 | self.assertEqual(obj1.doubler(JavaNull(str)), "Can't double NULL strings") 356 | 357 | def test_null_return(self): 358 | "Returned NULL objects are typed" 359 | Example = JavaClass('org/beeware/rubicon/test/Example') 360 | obj = Example() 361 | 362 | Thing = JavaClass('org/beeware/rubicon/test/Thing') 363 | 364 | obj.set_thing(Thing.__null__) 365 | returned = obj.get_thing() 366 | # Typed null objects are always equal to equivalent typed nulls 367 | self.assertEqual(returned, Thing.__null__) 368 | # All Typed nulls are equivalent 369 | self.assertEqual(returned, Example.__null__) 370 | # Null is always false 371 | self.assertFalse(returned) 372 | 373 | def test_java_null_construction(self): 374 | "Java NULLs can be constructed" 375 | Example = JavaClass('org/beeware/rubicon/test/Example') 376 | obj1 = Example() 377 | 378 | # Java nulls can be constructed explicitly 379 | self.assertEqual(JavaNull(b"Lcom/example/Thing;")._signature, b"Lcom/example/Thing;") 380 | 381 | # Java nulls can be constructed from a JavaClass 382 | self.assertEqual(JavaNull(Example)._signature, b"Lorg/beeware/rubicon/test/Example;") 383 | 384 | # Java nulls can be constructed from an instance 385 | self.assertEqual(JavaNull(obj1)._signature, b"Lorg/beeware/rubicon/test/Example;") 386 | 387 | # A Java Null can be constructed for Python or JNI primitives 388 | self.assertEqual(JavaNull(int)._signature, b"I") 389 | self.assertEqual(JavaNull(jlong)._signature, b"J") 390 | self.assertEqual(JavaNull(str)._signature, b"Ljava/lang/String;") 391 | self.assertEqual(JavaNull(jstring)._signature, b"Ljava/lang/String;") 392 | 393 | # Bytes is converted directly to an array of byte 394 | self.assertEqual(JavaNull(bytes)._signature, b"[B") 395 | 396 | # Some types can't be converted 397 | with self.assertRaises(ValueError): 398 | JavaNull(None) 399 | 400 | # A Java Null for an array of primitives can be defined with a list 401 | # of Python or JNI primitives 402 | self.assertEqual(JavaNull([int])._signature, b"[I") 403 | self.assertEqual(JavaNull([jlong])._signature, b"[J") 404 | 405 | # A Java Null for an array of objects can be defined with a list 406 | self.assertEqual(JavaNull([Example])._signature, b"[Lorg/beeware/rubicon/test/Example;") 407 | 408 | # A Java Null for an array of explicit JNI references 409 | self.assertEqual(JavaNull([b'Lcom/example/Thing;'])._signature, b"[Lcom/example/Thing;") 410 | 411 | # Arrays are defined with a type, not a literal 412 | with self.assertRaises(ValueError): 413 | JavaNull([1]) 414 | 415 | # Arrays must have a single element 416 | with self.assertRaises(ValueError): 417 | JavaNull([]) 418 | 419 | # Arrays can *only* have a single element 420 | with self.assertRaises(ValueError): 421 | JavaNull([int, int]) 422 | 423 | # Some types can't be converted in a list 424 | with self.assertRaises(ValueError): 425 | JavaNull([None]) 426 | 427 | def test_null_repr(self): 428 | "Null objects can be output to console" 429 | # Output of a null makes sense 430 | self.assertEqual(repr(JavaNull(b'Lcom/example/Thing;')), "") 431 | self.assertEqual(repr(JavaNull(str)), "") 432 | self.assertEqual(repr(JavaNull(int)), "") 433 | 434 | self.assertEqual(str(JavaNull(b'Lcom/example/Thing;')), "") 435 | self.assertEqual(str(JavaNull(str)), "") 436 | self.assertEqual(str(JavaNull(int)), "") 437 | 438 | def test_polymorphic_static_method(self): 439 | "Check that the right static method is activated based on arguments used" 440 | Example = JavaClass('org/beeware/rubicon/test/Example') 441 | 442 | self.assertEqual(Example.tripler(42), 126) 443 | self.assertEqual(Example.tripler("wibble"), "wibblewibblewibble") 444 | 445 | # If arguments don't match available options, an error is raised 446 | with self.assertRaises(ValueError): 447 | Example.tripler(1.234) 448 | 449 | def test_type_cast(self): 450 | "An object can be cast to another type" 451 | Example = JavaClass('org/beeware/rubicon/test/Example') 452 | obj1 = Example() 453 | 454 | Thing = JavaClass('org/beeware/rubicon/test/Thing') 455 | thing = Thing('This is thing', 2) 456 | 457 | obj1.set_thing(thing) 458 | 459 | # Retrieve a generic reference to the object (java.lang.Object) 460 | obj = obj1.get_generic_thing() 461 | 462 | ICallback_null = JavaNull(b'Lorg/beeware/rubicon/test/ICallback;') 463 | 464 | # Attempting to use this generic object *as* a Thing will fail. 465 | with self.assertRaises(AttributeError): 466 | obj.currentCount() 467 | with self.assertRaises(ValueError): 468 | obj1.combiner(3, "Ham", obj, ICallback_null, JavaNull([int])) 469 | 470 | # ...but if we cast it to the type we know it is 471 | # (org.beeware.rubicon.test.Thing), the same calls will succeed. 472 | cast_thing = Thing.__cast__(obj) 473 | self.assertEqual(cast_thing.currentCount(), 2) 474 | self.assertEqual( 475 | obj1.combiner(4, "Ham", cast_thing, ICallback_null, JavaNull([int])), 476 | "4::Ham::This is thing 2::::" 477 | ) 478 | 479 | # We can also cast as a global JNI reference 480 | # (org.beeware.rubicon.test.Thing), the same calls will succeed. 481 | global_cast_thing = Thing.__cast__(obj, globalref=True) 482 | self.assertEqual( 483 | obj1.combiner(4, "Ham", global_cast_thing, ICallback_null, JavaNull([int])), 484 | "4::Ham::This is thing 2::::" 485 | ) 486 | 487 | def test_convert_to_global(self): 488 | "An object can be converted into a global JNI reference." 489 | Example = JavaClass('org/beeware/rubicon/test/Example') 490 | obj1 = Example() 491 | 492 | Thing = JavaClass('org/beeware/rubicon/test/Thing') 493 | thing = Thing('This is thing', 2) 494 | 495 | ICallback__null = JavaNull(b'Lorg/beeware/rubicon/test/ICallback;') 496 | 497 | global_thing = thing.__global__() 498 | self.assertEqual(global_thing.currentCount(), 2) 499 | self.assertEqual( 500 | obj1.combiner(5, "Spam", global_thing, ICallback__null, JavaNull([int])), 501 | "5::Spam::This is thing 2::::" 502 | ) 503 | 504 | def test_pass_int_array(self): 505 | """A list of Python ints can be passed as a Java int array.""" 506 | Example = JavaClass("org/beeware/rubicon/test/Example") 507 | self.assertEqual(3, Example.sum_all_ints([1, 2])) 508 | 509 | def test_heterogenous_list(self): 510 | """A list of mixed types raise an exception when trying to find the right Java method.""" 511 | Example = JavaClass("org/beeware/rubicon/test/Example") 512 | with self.assertRaises(ValueError): 513 | Example.sum_all_ints(["two", 3]) 514 | with self.assertRaises(ValueError): 515 | Example.sum_all_ints([1, "two"]) 516 | with self.assertRaises(ValueError): 517 | Example.sum_all_floats([1.0, "two"]) 518 | with self.assertRaises(ValueError): 519 | Example.sum_all_doubles([1.0, "two"]) 520 | 521 | def test_list_that_cannot_be_turned_into_java_primitive_array(self): 522 | """A list that can't turn into a Java primitive array raises an exception when trying to find the right 523 | Java method.""" 524 | Example = JavaClass("org/beeware/rubicon/test/Example") 525 | with self.assertRaises(ValueError): 526 | Example.sum_all_ints([object()]) 527 | 528 | def test_empty_list(self): 529 | """An empty list results in an inability to find the right Java method.""" 530 | Example = JavaClass("org/beeware/rubicon/test/Example") 531 | with self.assertRaises(ValueError): 532 | Example.sum_all_ints([]) 533 | 534 | def test_pass_double_array(self): 535 | """A list of Python floats can be passed as a Java double array.""" 536 | Example = JavaClass("org/beeware/rubicon/test/Example") 537 | self.assertEqual(3, Example.sum_all_doubles([1.0, 2.0])) 538 | 539 | def test_pass_float_array(self): 540 | """A list of Python floats can be passed as a Java float array.""" 541 | Example = JavaClass("org/beeware/rubicon/test/Example") 542 | self.assertEqual(3, Example.sum_all_floats([1.0, 2.0])) 543 | 544 | def test_pass_boolean_array(self): 545 | """A list of Python bools can be passed as a Java boolean array.""" 546 | Example = JavaClass("org/beeware/rubicon/test/Example") 547 | self.assertEqual(False, Example.combine_booleans_by_and([True, False])) 548 | self.assertEqual(True, Example.combine_booleans_by_and([True, True])) 549 | 550 | def test_pass_byte_array(self): 551 | """A Python bytes object can be passed as a Java byte array.""" 552 | Example = JavaClass("org/beeware/rubicon/test/Example") 553 | self.assertEqual(ord(b'x'), Example.xor_all_bytes(b'x\x00')) 554 | self.assertEqual(0, Example.xor_all_bytes(b'xx')) 555 | 556 | def test_static_access_non_static(self): 557 | "An instance field/method cannot be accessed from the static context" 558 | Example = JavaClass('org/beeware/rubicon/test/Example') 559 | 560 | obj = Example() 561 | 562 | with self.assertRaises(AttributeError): 563 | obj.static_int_field 564 | 565 | with self.assertRaises(AttributeError): 566 | obj.get_static_int_field() 567 | 568 | def test_non_static_access_static(self): 569 | "A static field/method cannot be accessed from an instance context" 570 | Example = JavaClass('org/beeware/rubicon/test/Example') 571 | 572 | with self.assertRaises(AttributeError): 573 | Example.int_field 574 | 575 | with self.assertRaises(AttributeError): 576 | Example.get_int_field() 577 | 578 | def test_string_argument(self): 579 | "A method with a string argument can be passed." 580 | Example = JavaClass('org/beeware/rubicon/test/Example') 581 | example = Example() 582 | self.assertEqual(example.duplicate_string("Wagga"), "WaggaWagga") 583 | 584 | def test_string_return(self): 585 | "If a method or field returns a string, you get a Python string back" 586 | Example = JavaClass('org/beeware/rubicon/test/Example') 587 | example = Example() 588 | self.assertEqual(example.toString(), "This is a Java Example object") 589 | 590 | def test_float_method(self): 591 | "A method with a float arguments can be handled." 592 | Example = JavaClass('org/beeware/rubicon/test/Example') 593 | example = Example() 594 | self.assertEqual(example.area_of_square(1.5), 2.25) 595 | 596 | def test_double_method(self): 597 | "A method with a double arguments can be handled." 598 | Example = JavaClass('org/beeware/rubicon/test/Example') 599 | example = Example() 600 | self.assertEqual(example.area_of_circle(1.5), 1.5 * math.pi) 601 | 602 | def test_enum_method(self): 603 | "A method with enum arguments can be handled." 604 | Example = JavaClass('org/beeware/rubicon/test/Example') 605 | example = Example() 606 | 607 | Stuff = JavaClass('org/beeware/rubicon/test/Example$Stuff') 608 | 609 | self.assertEqual(example.label(Stuff.FOO), "Foo") 610 | self.assertEqual(example.label(Stuff.BAR), "Bar") 611 | self.assertEqual(example.label(Stuff.WHIZ), "Whiz") 612 | 613 | def test_object_return(self): 614 | "If a method or field returns an object, you get an instance of that type returned" 615 | Example = JavaClass('org/beeware/rubicon/test/Example') 616 | example = Example() 617 | 618 | Thing = JavaClass('org/beeware/rubicon/test/Thing') 619 | thing = Thing('This is thing', 2) 620 | 621 | example.set_thing(thing) 622 | 623 | the_thing = example.get_thing() 624 | self.assertEqual(the_thing.toString(), "This is thing 2") 625 | 626 | def test_interface(self): 627 | "An Java interface can be defined in Python and proxied." 628 | ICallback = JavaInterface('org/beeware/rubicon/test/ICallback') 629 | 630 | results = {} 631 | 632 | class MyInterface(ICallback): 633 | def __init__(self, value): 634 | super(MyInterface, self).__init__() 635 | self.value = value 636 | 637 | def poke(self, example, value): 638 | results['string'] = example.toString() 639 | results['int'] = value + self.value 640 | 641 | def peek(self, example, value): 642 | results['string'] = example.toString() 643 | results['int'] = value + self.value 644 | 645 | # Create two handler instances so we can check the right one 646 | # is being invoked. 647 | handler1 = MyInterface(5) # NOQA; F841 648 | handler2 = MyInterface(10) 649 | 650 | # Create an Example object, and register a handler with it. 651 | Example = JavaClass('org/beeware/rubicon/test/Example') 652 | example = Example() 653 | example.set_callback(handler2) 654 | 655 | # Invoke the callback; check that the results have been peeked as expected 656 | example.test_peek(42) 657 | 658 | self.assertEqual(results['string'], 'This is a Java Example object') 659 | self.assertEqual(results['int'], 52) 660 | 661 | example.test_poke(37) 662 | 663 | self.assertEqual(results['string'], 'This is a Java Example object') 664 | self.assertEqual(results['int'], 47) 665 | 666 | def test_interface_int_return(self): 667 | """A Java interface with an int-returning method can be defined in Python and proxied, 668 | including return value.""" 669 | # To ensure Java can call into our Python proxy object and get an `int` out, we 670 | # ask AddOne to call a Python class which implements a Java interface with a 671 | # method to return an `int`. 672 | # 673 | # For consistency with previous tests, and to verify that parameter passing works 674 | # with int-returning interfaces, we rely on an `Example` object to store state. 675 | ICallbackInt = JavaInterface('org/beeware/rubicon/test/ICallbackInt') 676 | 677 | class GetAndIncrementExampleIntField(ICallbackInt): 678 | def getInt(self, example): 679 | example.set_int_field(example.int_field + 1) 680 | return example.int_field 681 | 682 | implementation = GetAndIncrementExampleIntField() 683 | 684 | # Create two `Example` objects to validate there are no weird surprises about where state goes. 685 | Example = JavaClass('org/beeware/rubicon/test/Example') 686 | example1 = Example() 687 | example2 = Example() 688 | 689 | # Validate that `AddOne` calls our Python `getInt()` implementation. The first number is 35 690 | # because AddOne adds 1, and getInt() adds 1, and it starts at 33. 691 | AddOne = JavaClass('org/beeware/rubicon/test/AddOne') 692 | self.assertEqual(35, AddOne().addOne(implementation, example1)) 693 | self.assertEqual(36, AddOne().addOne(implementation, example1)) 694 | # Validate that implementation mutates example1's state, not example2's state. 695 | self.assertEqual(35, AddOne().addOne(implementation, example2)) 696 | 697 | def test_interface_bool_return(self): 698 | """A Java interface with a bool-returning method can be defined in Python and proxied, 699 | including return value.""" 700 | ICallbackBool = JavaInterface('org/beeware/rubicon/test/ICallbackBool') 701 | 702 | class MakeBool(ICallbackBool): 703 | def __init__(self, s): 704 | super().__init__() 705 | self.s = s 706 | 707 | def getBool(self): 708 | if self.s == "yes": 709 | return True 710 | return False 711 | 712 | true_maker = MakeBool("yes") 713 | false_maker = MakeBool("no") 714 | 715 | # Validate that a Java class can use our Python class when expecting the bool-returning interface. 716 | TruthInverter = JavaClass("org/beeware/rubicon/test/TruthInverter") 717 | self.assertFalse(TruthInverter().invert(true_maker)) 718 | self.assertTrue(TruthInverter().invert(false_maker)) 719 | 720 | def test_alternatives(self): 721 | "A class is aware of it's type hierarchy" 722 | Example = JavaClass('org/beeware/rubicon/test/Example') 723 | 724 | self.assertEqual( 725 | Example.__dict__['_alternates'], 726 | [ 727 | b"Lorg/beeware/rubicon/test/Example;", 728 | b"Lorg/beeware/rubicon/test/BaseExample;", 729 | b"Ljava/lang/Object;", 730 | ]) 731 | 732 | AbstractCallback = JavaClass('org/beeware/rubicon/test/AbstractCallback') 733 | self.assertEqual( 734 | AbstractCallback.__dict__['_alternates'], 735 | [ 736 | b"Lorg/beeware/rubicon/test/AbstractCallback;", 737 | b"Lorg/beeware/rubicon/test/ICallback;", 738 | b"Ljava/lang/Object;", 739 | ]) 740 | 741 | String = JavaClass('java/lang/String') 742 | 743 | self.assertEqual( 744 | String.__dict__['_alternates'], 745 | [ 746 | b"Ljava/lang/String;", 747 | b"Ljava/io/Serializable;", 748 | b"Ljava/lang/Comparable;", 749 | b"Ljava/lang/CharSequence;", 750 | b"Ljava/lang/Object;", 751 | ]) 752 | 753 | def test_inner(self): 754 | "Inner classes can be accessed" 755 | 756 | Inner = JavaClass('org/beeware/rubicon/test/Example$Inner') 757 | 758 | self.assertEqual(Inner.INNER_CONSTANT, 1234) 759 | 760 | def test_dunder_main(self): 761 | "When launched from `rubicon-java`, sys.modules should have a `__main__` module." 762 | self.assertEqual('module', sys.modules['__main__'].__class__.__name__) 763 | 764 | 765 | class ExampleClassWithCleanup(object): 766 | '''Returns the `Example` JavaClass, wrapped in a context manager 767 | to save/restore two class fields. 768 | 769 | Use this instead of `JavaClass('org/beeware/rubicon/test/Example')` when 770 | you want to mutate the static fields from tests. 771 | ''' 772 | 773 | def __enter__(self): 774 | Example = JavaClass('org/beeware/rubicon/test/Example') 775 | self._initial_base = Example.get_static_base_int_field() 776 | self._initial = Example.get_static_int_field() 777 | self._klass = Example 778 | return Example 779 | 780 | def __exit__(self, *args): 781 | self._klass.set_static_base_int_field(self._initial_base) 782 | self._klass.set_static_int_field(self._initial) 783 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | # Tox (http://tox.testrun.org/) is a tool for running tests 2 | # in multiple virtualenvs. This configuration file will run the 3 | # test suite on all supported python versions. To use it, "pip install tox" 4 | # and then run "tox" from this directory. 5 | 6 | [tox] 7 | envlist = flake8,towncrier-check,docs,package,py{36,37,38,39,310,311},pypy3 8 | skip_missing_interpreters = true 9 | 10 | [testenv] 11 | whitelist_externals = 12 | make 13 | java 14 | passenv = 15 | JAVA_HOME 16 | LIBRARY_PATH 17 | PYTHON_CONFIG 18 | commands = 19 | make clean 20 | make all 21 | make test 22 | 23 | [testenv:flake8] 24 | skip_install = True 25 | deps = 26 | flake8 27 | commands = flake8 {posargs} 28 | 29 | [testenv:towncrier-check] 30 | skip_install = True 31 | deps = 32 | {[testenv:towncrier]deps} 33 | commands = 34 | python -m towncrier.check --compare-with origin/main 35 | 36 | [testenv:towncrier] 37 | skip_install = True 38 | deps = 39 | towncrier == 21.9.0 40 | commands = 41 | towncrier {posargs} 42 | 43 | [testenv:docs] 44 | deps = 45 | -r{toxinidir}/docs/requirements_docs.txt 46 | commands = 47 | python setup.py build_sphinx -W 48 | 49 | [testenv:package] 50 | deps = 51 | check_manifest 52 | wheel 53 | twine 54 | commands = 55 | check-manifest -v 56 | python setup.py sdist bdist_wheel 57 | python -m twine check dist/* 58 | 59 | [testenv:publish] 60 | deps = 61 | wheel 62 | twine 63 | passenv = 64 | TWINE_USERNAME 65 | TWINE_PASSWORD 66 | commands = 67 | python -m twine upload dist/* 68 | --------------------------------------------------------------------------------