I'm playing around with my new toy, JCC 2.21, and am having trouble implementing callbacks in a python script. I have wrapped the following simple Java thread API and am calling it from python 2.7 (CPython), but when I call the JccTest.addJccTestListener(JccTestListener)
method, the JVM reports a null argument.
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
public class JccTest implements Runnable {
private final Object listenersLock = new Object();
private final List<JccTestListener> listeners = new ArrayList<JccTestListener>();
private final AtomicBoolean running = new AtomicBoolean(false);
private final AtomicBoolean finished = new AtomicBoolean(false);
public void start() {
if (running.compareAndSet(false, true)) {
new Thread(this).start();
}
}
public void stop() {
finished.set(true);
}
public void addJccTestListener(JccTestListener l) {
if (l == null) {
throw new IllegalArgumentException("argument must be non-null");
}
synchronized (listenersLock) {
listeners.add(l);
}
}
public void removeJccTestListener(JccTestListener l) {
synchronized (listenersLock) {
listeners.remove(l);
}
}
@Override
public void run() {
System.out.println("Start");
while (!finished.get()) {
System.out.println("Notifiying listeners");
synchronized (listenersLock) {
for (JccTestListener l : listeners) {
System.out.println("Notifiying " + String.valueOf(l));
l.message("I'm giving you a message!");
}
}
System.out.println("Sleeping");
try {
Thread.sleep(5000);
} catch (InterruptedException ex) {
continue;
}
}
running.set(false);
System.out.println("Stop");
}
public static void main(String[] args) throws InterruptedException {
JccTest test = new JccTest();
test.addJccTestListener(new JccTestListener() {
@Override
public void message(String msg) {
// called from another thread
System.out.println(msg);
}
});
test.start();
Thread.sleep(10000);
test.stop();
}
}
public interface JccTestListener {
public void message(String msg);
}
Generated wrapper with:
python -m jcc --jar jcc-test.jar --python jcc_test --build --install
And then executed this script (equivalent to the main method of JccTest
):
import jcc_test
import time, sys
jcc_test.initVM(jcc_test.CLASSPATH)
test = jcc_test.JccTest()
class MyListener(jcc_test.JccTestListener):
def __init__(self):
pass
def message(self, msg):
print msg
test.addJccTestListener(MyListener())
test.start()
time.sleep(10)
test.stop()
sys.exit(0)
Which results in:
"python.exe" jcc_test_test.py
Traceback (most recent call last):
File "jcc_test_test.py", line 16, in <module>
test.addJccTestListener(MyListener())
jcc_test.JavaError: java.lang.IllegalArgumentException: argument must be non-null
Java stacktrace:
java.lang.IllegalArgumentException: argument must be non-null
at com.example.jcc.JccTest.addJccTestListener(JccTest.java:32)
Besides the null listener instance, is doing something like this even possible with CPython? I've read that in its implementation only one thread may execute the python script at a time, which might (?) be a problem for me. Doing something like this with Jython was trivial.
I'm rather new to python so please be gentle.
Figured it out. You need to define a pythonic extension for a java class to make this work. The detailed procedure is described in JCC documentation (Writing Java class extensions in Python) and is rather simple.
First, code a class that implements your interface and add some magic markers that are recognized by JCC and affect what the wrapper generator will generate.
The markers are denoted by my comments and must appear verbatim in any class that is to be extended in python. My implementation delegates the interface method to a native implementation method, which will be extended in python.
Then generate the wrapper as usual:
And finally make a python extension for the new class:
This now works as expected with callbacks coming in.