├── .gitignore ├── .mailmap ├── .travis.yml ├── .travis └── build.sh ├── README.md ├── examples └── scdemo.py ├── pom.xml └── src ├── main ├── c │ └── cpython-startup.c ├── java │ └── org │ │ └── scijava │ │ └── plugins │ │ └── scripting │ │ └── cpython │ │ ├── CPythonScriptEngine.java │ │ ├── CPythonScriptLanguage.java │ │ └── CPythonStartup.java └── resources │ └── scripting-cpython.py └── test └── java └── org └── scijava └── plugins └── scripting └── cpython └── CPythonTest.java /.gitignore: -------------------------------------------------------------------------------- 1 | # Eclipse # 2 | /.classpath 3 | /.project 4 | /.settings/ 5 | 6 | # Maven # 7 | /target/ 8 | -------------------------------------------------------------------------------- /.mailmap: -------------------------------------------------------------------------------- 1 | Lee Kamentsky 2 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: java 2 | jdk: openjdk8 3 | branches: 4 | only: 5 | - master 6 | - "/.*-[0-9]+\\..*/" 7 | install: true 8 | script: ".travis/build.sh" 9 | env: 10 | global: 11 | - secure: gAQvFCQxRjDT/xCivdxLEADu3DcXoX8QGEYdRktKsSRyEcUW+T6J1kEHtNSdFxhxENazXXyAVk+sadwAvy3NRI5PDYsDLfW0giLnsrTLWYO+f9z8jCVgMfj3RPk8bxyJW8j1sbnGmGo0EzjddBkyIx3KQl3utj7WpkchltNKdTo= 12 | - secure: GeYKR/MMExrgu/b8+FUUvY/mfdimIuzXqwzhLqCYWgkje8Dc0hgTpDu9OJUWznGzHB6oE8nBmnpKHvdfvXYjoEWmqjd4y887S0nnckkKTYIpM7edNmzKt4ysPmp7/wZWJ6qdrwKSmKGSHKn/YMzLGY+tXpntQQANvdBnBuykEE8= 13 | -------------------------------------------------------------------------------- /.travis/build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | curl -fsLO https://raw.githubusercontent.com/scijava/scijava-scripts/master/travis-build.sh 3 | sh travis-build.sh 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # (Native) Python Scripting 2 | 3 | This library provides a 4 | [JSR-223-compliant](https://en.wikipedia.org/wiki/Scripting_for_the_Java_Platform) 5 | scripting plugin for [Python](http://www.python.org/). In contrast to the 6 | [Jython support](https://github.com/scijava/scripting-jython), this library 7 | accesses CPython (i.e. native Python) via JNI. 8 | 9 | It is implemented as a `ScriptLanguage` plugin for the [SciJava 10 | Common](https://github.com/scijava/scijava-common) platform, which means that 11 | in addition to being usable directly as a `javax.script.ScriptEngineFactory`, 12 | it also provides some functionality on top, such as the ability to generate 13 | lines of script code based on SciJava events. 14 | 15 | For a complete list of scripting languages available as part of the SciJava 16 | platform, see the 17 | [Scripting](https://github.com/scijava/scijava-common/wiki/Scripting) page on 18 | the SciJava Common wiki. 19 | 20 | ## How to build in Eclipse (requires at least Eclipse Luna) 21 | 22 | Please note that a C compiler needs to be found via the command-line and that 23 | `python.h` needs to be found by the compiler. 24 | 25 | With these prerequisites in place, it should be as simple as 26 | *File>Import>Maven>Existing Maven Project...*. 27 | 28 | You might want to make sure that *target/nar/nar-generated* is added as 29 | a source directory to the project (if not, you might need to restart Eclipse). 30 | This directory is generated by the [NAR plugin](http://maven-nar.github.io/). 31 | 32 | ## Running from Python 33 | As things stand now, scripting-cpython relies on your installed version of 34 | Python and the [javabridge package](http://pythonhosted.org/javabridge/). 35 | You can install javabridge using PIP - the installation instructions are 36 | documented at the page referenced by the link above. 37 | 38 | As an example, here's how to start ImageJ 2.0 from Python: 39 | 40 | * Unzip the ImageJ application. 41 | * Put the scripting-cpython JAR in the application's `jars` folder 42 | 43 | Now assuming that you have the application in `/foo/ImageJ.app` and the 44 | library that maven compiled for you in `/bar/lib`, the following script 45 | will launch ImageJ with CPython scripting support: 46 | 47 | import os 48 | import javabridge 49 | path_imagej = "/foo/ImageJ.app/jars" 50 | jars = [os.path.join(path_imagej, x) 51 | for x in os.listdir(path_imagej): 52 | if x.endswith(".jar")] 53 | lib_path = os.environ["PATH"]+";/bar/lib" 54 | library_path_arg = "-Djava.library.path=%s" % lib_path 55 | javabridge.start_vm([library_path_arg], class_path=javabridge.JARS+jars) 56 | javabridge.activate_awt() 57 | env = javabridge.get_env() 58 | jargs = env.make_object_array(0, env.find_class("java/lang/String")) 59 | runnable = javabridge.run_script(""" 60 | new java.lang.Runnable() { 61 | run: function () { 62 | Packages.net.imagej.Main.launch(args); 63 | } };""", dict(args=jargs)) 64 | javabridge.execute_runnable_in_main_thread(runnable) 65 | 66 | You should be able to start a script editor from `File->New->Script...` and 67 | then choose `CPython` from the language menu and you should be good to go. 68 | 69 | ## The scripting language 70 | 71 | Inputs to your script are either translated to Python native types or, 72 | if they are Java types, they are wrapped using reflection. You can import 73 | classes into your local scope using `importClass`. Here is an example: 74 | 75 | importClass("java.lang.Integer") 76 | Integer.toString(Integer.MAX_VALUE) # returns 2^31 - 1 77 | 78 | and another: 79 | 80 | importClass("java.util.ArrayList") 81 | a = ArrayList() 82 | a.add("Hello") 83 | a.add("World") 84 | str(a) # returns [ Hello, World ] 85 | 86 | If you want to wrap an object retrieved from the javabridge, you can use 87 | JWrapper: 88 | 89 | import javabridge 90 | a = JWrapper(javabridge.make_instance("java/util/ArrayList", "()V")) 91 | a.add("Hello") 92 | a.add("World") 93 | str(a.size()) # returns 2 94 | -------------------------------------------------------------------------------- /examples/scdemo.py: -------------------------------------------------------------------------------- 1 | # @DisplayService d 2 | # @double frequency(min="1") 3 | # @double magnitude(min="1") 4 | # 5 | # Demo of manipulating an image using Numpy 6 | # Only works on B/W images (but the arrays that 7 | # are captured and witten back can be N-D) 8 | # 9 | import javabridge as J 10 | import numpy as np 11 | importClass("net.imagej.display.ImageDisplay") 12 | importClass("net.imglib2.util.ImgUtil") 13 | 14 | display = d.getActiveDisplay(ImageDisplay.klass) 15 | data = display.getActiveView().getData() 16 | imgplus = data.getImgPlus() 17 | ndims = imgplus.numDimensions() 18 | start = [imgplus.min(i) for i in range(ndims)] 19 | end = [imgplus.max(i)+1 for i in range(ndims)] 20 | dims = np.array(end) - np.array(start) 21 | a = np.zeros(np.prod(dims), np.float64) 22 | ja = J.get_env().make_double_array(np.ascontiguousarray(a)) 23 | strides = np.ones(len(dims), int) 24 | for i in range(0, len(dims)-1): 25 | strides[-i-2] = strides[-i-1] * dims[-i-1] 26 | 27 | ImgUtil.copy(imgplus, ja, 0, strides) 28 | a = J.get_env().get_double_array_elements(ja) 29 | a.shape = dims 30 | # 31 | # OK now apply a little amateurish warping. 32 | # 33 | i, j = np.mgrid[0:dims[0], 0:dims[1]].astype(float) 34 | id = np.sin(2*np.pi * i * frequency / dims[0]) * magnitude 35 | jd = np.sin(2*np.pi * j * frequency / dims[1]) * magnitude 36 | ii = np.maximum(0, np.minimum(dims[0]-1, i+id)).astype(int) 37 | jj = np.maximum(0, np.minimum(dims[1]-1, j+jd)).astype(int) 38 | b = a[ii, jj] 39 | ImgUtil.copy(b.flatten(), 0, strides, imgplus) 40 | display.update() 41 | 42 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.0.0 4 | 5 | 6 | org.scijava 7 | pom-scijava 8 | 26.0.0 9 | 10 | 11 | 12 | scripting-cpython 13 | 0.1.0-SNAPSHOT 14 | nar 15 | 16 | SciJava Scripting: CPython 17 | JSR-223-compliant Python scripting language plugin linking to CPython 18 | https://github.com/scijava/scripting-cpython 19 | 2014 20 | 21 | 22 | Simplified BSD License 23 | repo 24 | 25 | 26 | 27 | 28 | 29 | ctrueden 30 | Curtis Rueden 31 | https://imagej.net/User:Rueden 32 | 33 | maintainer 34 | 35 | 36 | 37 | 38 | 39 | Lee Kamentsky 40 | https://imagej.net/User:Leek 41 | founder 42 | LeeKamentsky 43 | 44 | 45 | Johannes Schindelin 46 | https://imagej.net/User:Schindelin 47 | founder 48 | dscho 49 | 50 | 51 | 52 | 53 | scm:git:git://github.com/scijava/scripting-cpython 54 | scm:git:git@github.com:scijava/scripting-cpython 55 | HEAD 56 | https://github.com/scijava/scripting-cpython 57 | 58 | 59 | GitHub Issues 60 | https://github.com/scijava/scripting-cpython/issues 61 | 62 | 63 | Travis CI 64 | https://travis-ci.org/scijava/scripting-cpython 65 | 66 | 67 | 68 | 2.6 69 | -I/usr/include/python${python.version} 70 | -lpython${python.version} 71 | 72 | true 73 | 74 | 75 | deploy-to-scijava 76 | 77 | 78 | 79 | 80 | 81 | org.scijava 82 | scijava-common 83 | 84 | 85 | 86 | 87 | junit 88 | junit 89 | test 90 | 91 | 92 | 93 | 94 | integration-test 95 | 96 | 97 | maven-jar-plugin 98 | 99 | 100 | 101 | org.scijava.plugins.scripting.cpython 102 | 103 | 104 | 105 | 106 | 107 | org.codehaus.mojo 108 | license-maven-plugin 109 | 110 | bsd_2 111 | Board of Regents of the University of 112 | Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck 113 | Institute of Molecular Cell Biology and Genetics. 114 | 115 | 116 | 117 | com.github.maven-nar 118 | nar-maven-plugin 119 | 3.2.0 120 | true 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | jni 135 | org.scijava.plugins.scripting.cpython 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | windows-python-2.7 146 | 147 | 148 | c:/python27 149 | 150 | 151 | 152 | 27 153 | -Ic:/python${python.version}/include 154 | c:/python${python.version}/libs/python${python.version}.lib 155 | 156 | 157 | 158 | 159 | -------------------------------------------------------------------------------- /src/main/c/cpython-startup.c: -------------------------------------------------------------------------------- 1 | /* 2 | * #%L 3 | * JSR-223-compliant Python scripting language plugin linking to CPython 4 | * %% 5 | * Copyright (C) 2014 Board of Regents of the University of 6 | * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck 7 | * Institute of Molecular Cell Biology and Genetics. 8 | * %% 9 | * Redistribution and use in source and binary forms, with or without 10 | * modification, are permitted provided that the following conditions are met: 11 | * 12 | * 1. Redistributions of source code must retain the above copyright notice, 13 | * this list of conditions and the following disclaimer. 14 | * 2. Redistributions in binary form must reproduce the above copyright notice, 15 | * this list of conditions and the following disclaimer in the documentation 16 | * and/or other materials provided with the distribution. 17 | * 18 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 19 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 21 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE 22 | * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 23 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 24 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 25 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 26 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 27 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 28 | * POSSIBILITY OF SUCH DAMAGE. 29 | * #L% 30 | */ 31 | 32 | #include "org_scijava_plugins_scripting_cpython_CPythonStartup.h" 33 | #include 34 | 35 | JNIEXPORT void JNICALL Java_org_scijava_plugins_scripting_cpython_CPythonStartup_initializePythonThread(JNIEnv *env, jclass clazz, jstring pythonCode) 36 | { 37 | PyGILState_STATE state; 38 | const char *python_code; 39 | PyObject *err; 40 | 41 | Py_Initialize(); 42 | state = PyGILState_Ensure(); 43 | python_code = (*env)->GetStringUTFChars(env, pythonCode, NULL); 44 | PyRun_SimpleString(python_code); 45 | (*env)->ReleaseStringUTFChars(env, pythonCode, python_code); 46 | err = PyErr_Occurred(); 47 | if (err) { 48 | PyErr_Print(); 49 | PyErr_Clear(); 50 | } 51 | PyGILState_Release(state); 52 | if (err) { 53 | (*env)->ThrowNew(env, (*env)->FindClass(env, "java/lang/RuntimeException"), "Python script raised an exception"); 54 | } 55 | 56 | /* 57 | * We cannot really call Py_Finalize(); here: multiple SciJava contexts 58 | * can have their individual CPythonScriptLanguage instances, so we 59 | * cannot call it after tearing down the context, either, so we have to 60 | * introduce a reference counter in the C library (TODO). 61 | */ 62 | } 63 | -------------------------------------------------------------------------------- /src/main/java/org/scijava/plugins/scripting/cpython/CPythonScriptEngine.java: -------------------------------------------------------------------------------- 1 | /* 2 | * #%L 3 | * Scripting-CPython JSR 233-compliant script binding to CPython 4 | * via the javabridge. 5 | * %% 6 | * Copyright (C) 2009 - 2014 Board of Regents of the University of 7 | * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck 8 | * Institute of Molecular Cell Biology and Genetics. 9 | * %% 10 | * Redistribution and use in source and binary forms, with or without 11 | * modification, are permitted provided that the following conditions are met: 12 | * 13 | * 1. Redistributions of source code must retain the above copyright notice, 14 | * this list of conditions and the following disclaimer. 15 | * 2. Redistributions in binary form must reproduce the above copyright notice, 16 | * this list of conditions and the following disclaimer in the documentation 17 | * and/or other materials provided with the distribution. 18 | * 19 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 20 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 21 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 22 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE 23 | * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 24 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 25 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 26 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 27 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 28 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 29 | * POSSIBILITY OF SUCH DAMAGE. 30 | * #L% 31 | */ 32 | package org.scijava.plugins.scripting.cpython; 33 | 34 | import java.io.IOException; 35 | import java.io.Reader; 36 | import java.util.Arrays; 37 | import java.util.Collections; 38 | import java.util.HashMap; 39 | import java.util.List; 40 | import java.util.Map; 41 | import java.util.concurrent.SynchronousQueue; 42 | 43 | import javax.script.Bindings; 44 | import javax.script.ScriptContext; 45 | import javax.script.ScriptException; 46 | import javax.script.SimpleBindings; 47 | 48 | import org.scijava.script.AbstractScriptEngine; 49 | 50 | 51 | /** 52 | * @author Lee Kamentsky 53 | * 54 | * The script engine for CPython scripting via the javabridge. 55 | * 56 | * The engine communicates with a Python thread via two static queues 57 | * which are used to arrange for the creation of an execution context 58 | * for the engine via two instance-specific queues. 59 | */ 60 | public class CPythonScriptEngine extends AbstractScriptEngine { 61 | 62 | public static final SynchronousQueue engineRequestQueue = new SynchronousQueue(); 63 | public static final SynchronousQueue engineResponseQueue = new SynchronousQueue(); 64 | 65 | private final SynchronousQueue requestQueue = new SynchronousQueue(); 66 | private final SynchronousQueue responseQueue = new SynchronousQueue(); 67 | 68 | /** 69 | * @author Lee Kamentsky 70 | * The commands that can be passed over the queues 71 | */ 72 | public static enum EngineCommands { 73 | /** 74 | * Sent via the engineRequestQueue: create a new engine context 75 | */ 76 | NEW_ENGINE, 77 | /** 78 | * Sent via the requestQueue: execute a script 79 | */ 80 | EXECUTE, 81 | /** 82 | * Send via the requestQueue: evaluate a script, returning 83 | * a Java object representing the result. 84 | * 85 | * The payload's first argument is a string to be executed. 86 | * The payload's second argument is a {@code Map} that's 87 | * used to populate the local context of the script evaluation. 88 | */ 89 | EVALUATE, 90 | /** 91 | * Sent via the engineResponseQueue: the result of an NEW_ENGINE request 92 | * 93 | * The payload's first argument is a string to be executed. 94 | * The payload's second argument is a {@code Map} that's 95 | * used to populate the local context of the script evaluation. 96 | */ 97 | NEW_ENGINE_RESULT, 98 | /** 99 | * Sent via the responseQueue: indicates successful script execution 100 | */ 101 | EXECUTION, 102 | /** 103 | * Sent via the responseQueue: the result of an evaluation 104 | * The payload contains the result 105 | */ 106 | EVALUATE_RESULT, 107 | /** 108 | * Sent via the responseQueue: an exception occurred during execution or evaluation 109 | * The payload contains a Java exception 110 | */ 111 | EXCEPTION, 112 | /** 113 | * Sent via the requestQueue: close and destroy the Python side of the engine 114 | * There is no response. 115 | */ 116 | CLOSE_ENGINE, 117 | /** 118 | * Sent when closing the service 119 | */ 120 | CLOSE_SERVICE 121 | 122 | }; 123 | public static class Message { 124 | final public EngineCommands command; 125 | final public List payload; 126 | public Message(EngineCommands command, List payload) { 127 | this.command = command; 128 | this.payload = payload; 129 | } 130 | /* (non-Javadoc) 131 | * @see java.lang.Object#toString() 132 | */ 133 | @Override 134 | public String toString() { 135 | return String.format("Command=%s, payload=%s", command, payload.toString() ); 136 | } 137 | } 138 | 139 | public CPythonScriptEngine() throws InterruptedException { 140 | engineRequestQueue.put(new Message(EngineCommands.NEW_ENGINE, Arrays.asList((Object)requestQueue, (Object)responseQueue))); 141 | engineResponseQueue.take(); 142 | engineScopeBindings = new SimpleBindings(); 143 | } 144 | /** 145 | * Tell the Python side that the service is finished 146 | * @throws InterruptedException 147 | */ 148 | static void closeService() throws InterruptedException { 149 | engineRequestQueue.put(new Message(EngineCommands.CLOSE_SERVICE, Collections.emptyList())); 150 | } 151 | @Override 152 | public Object eval(String script) throws ScriptException { 153 | // TODO: Populate context with current input bindings 154 | final Message request = new Message(EngineCommands.EVALUATE, Arrays.asList((Object)script, (Object)engineScopeBindings)); 155 | return eval(request); 156 | } 157 | /** 158 | * @param request 159 | * @return 160 | * @throws ScriptException 161 | */ 162 | private Object eval(final Message request) throws ScriptException { 163 | try { 164 | requestQueue.put(request); 165 | Message result = responseQueue.take(); 166 | if (result.command == EngineCommands.EXCEPTION) { 167 | if (result.payload.size() == 1) { 168 | Object oMessage = result.payload.get(0); 169 | if (oMessage instanceof String) { 170 | throw new ScriptException((String)oMessage); 171 | } else if (oMessage instanceof Exception) { 172 | throw new ScriptException((Exception)oMessage); 173 | } 174 | } 175 | throw new ScriptException("Exception thrown but unknown format"); 176 | } 177 | // TODO: Populate bindings with the context from the request's map 178 | return result.payload.get(0); 179 | } catch (InterruptedException e) { 180 | throw new ScriptException("Operation interrupted"); 181 | } 182 | } 183 | 184 | @Override 185 | public Object eval(Reader reader) throws ScriptException { 186 | StringBuilder buf = new StringBuilder(); 187 | char [] cbuf = new char [65536]; 188 | while (true) { 189 | try { 190 | int nChars = reader.read(cbuf); 191 | if (nChars <= 0) break; 192 | buf.append(cbuf, 0, nChars); 193 | } catch (IOException e) { 194 | throw new ScriptException(e); 195 | } 196 | } 197 | return eval(buf.toString()); 198 | } 199 | 200 | /* (non-Javadoc) 201 | * @see java.lang.Object#finalize() 202 | */ 203 | @Override 204 | protected void finalize() throws Throwable { 205 | requestQueue.put(new Message(EngineCommands.CLOSE_ENGINE, Collections.emptyList())); 206 | super.finalize(); 207 | } 208 | } 209 | -------------------------------------------------------------------------------- /src/main/java/org/scijava/plugins/scripting/cpython/CPythonScriptLanguage.java: -------------------------------------------------------------------------------- 1 | /* 2 | * #%L 3 | * SciJava Common shared library for SciJava software. 4 | * %% 5 | * Copyright (C) 2009 - 2014 Board of Regents of the University of 6 | * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck 7 | * Institute of Molecular Cell Biology and Genetics. 8 | * %% 9 | * Redistribution and use in source and binary forms, with or without 10 | * modification, are permitted provided that the following conditions are met: 11 | * 12 | * 1. Redistributions of source code must retain the above copyright notice, 13 | * this list of conditions and the following disclaimer. 14 | * 2. Redistributions in binary form must reproduce the above copyright notice, 15 | * this list of conditions and the following disclaimer in the documentation 16 | * and/or other materials provided with the distribution. 17 | * 18 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 19 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 21 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE 22 | * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 23 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 24 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 25 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 26 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 27 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 28 | * POSSIBILITY OF SUCH DAMAGE. 29 | * #L% 30 | */ 31 | 32 | package org.scijava.plugins.scripting.cpython; 33 | 34 | import java.io.IOException; 35 | import java.io.InputStream; 36 | import java.io.InputStreamReader; 37 | import java.io.Reader; 38 | 39 | import javax.script.ScriptEngine; 40 | 41 | import org.scijava.log.LogService; 42 | import org.scijava.plugin.Parameter; 43 | import org.scijava.plugin.Plugin; 44 | import org.scijava.script.AbstractScriptLanguage; 45 | import org.scijava.script.ScriptLanguage; 46 | 47 | /** 48 | * @author Lee Kamentsky 49 | * 50 | * A script language plugin for CPython and the Javabridge 51 | * 52 | */ 53 | @Plugin(type = ScriptLanguage.class) 54 | public class CPythonScriptLanguage extends AbstractScriptLanguage { 55 | private static final String PYTHON_SCRIPT = "scripting-cpython.py"; 56 | @Parameter 57 | LogService logService; 58 | 59 | boolean initialized=false; 60 | 61 | @Override 62 | public ScriptEngine getScriptEngine() { 63 | synchronized(this) { 64 | if (! initialized) { 65 | final InputStream is = this.getClass().getClassLoader().getResourceAsStream(PYTHON_SCRIPT); 66 | final Reader rdr = new InputStreamReader(is); 67 | final StringBuffer sbScript = new StringBuffer(); 68 | final char [] buffer = new char[65536]; 69 | while (true) { 70 | try { 71 | final int nBytes = rdr.read(buffer); 72 | if (nBytes <= 0) break; 73 | sbScript.append(buffer, 0, nBytes); 74 | } catch (IOException e) { 75 | logService.warn(String.format( 76 | "Unexpected read failure in CPython script language for %s resource: %s", 77 | PYTHON_SCRIPT, e.getMessage())); 78 | return null; 79 | } 80 | } 81 | CPythonStartup.initializePythonThread(sbScript.toString()); 82 | initialized=true; 83 | } 84 | } 85 | try { 86 | CPythonScriptEngine engine = new CPythonScriptEngine(); 87 | getContext().inject(engine); 88 | return engine; 89 | 90 | } catch (InterruptedException e) { 91 | logService.warn(e); 92 | return null; 93 | } 94 | } 95 | 96 | /* (non-Javadoc) 97 | * @see imagej.script.AbstractScriptLanguage#getEngineName() 98 | */ 99 | @Override 100 | public String getEngineName() { 101 | return "cpython"; 102 | } 103 | 104 | /* (non-Javadoc) 105 | * @see imagej.script.AbstractScriptLanguage#getLanguageName() 106 | */ 107 | @Override 108 | public String getLanguageName() { 109 | return "CPython"; 110 | } 111 | 112 | /* (non-Javadoc) 113 | * @see java.lang.Object#finalize() 114 | */ 115 | @Override 116 | protected void finalize() throws Throwable { 117 | CPythonScriptEngine.closeService(); 118 | super.finalize(); 119 | } 120 | 121 | } 122 | -------------------------------------------------------------------------------- /src/main/java/org/scijava/plugins/scripting/cpython/CPythonStartup.java: -------------------------------------------------------------------------------- 1 | /* 2 | * #%L 3 | * JSR-223-compliant Python scripting language plugin linking to CPython 4 | * %% 5 | * Copyright (C) 2014 Board of Regents of the University of 6 | * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck 7 | * Institute of Molecular Cell Biology and Genetics. 8 | * %% 9 | * Redistribution and use in source and binary forms, with or without 10 | * modification, are permitted provided that the following conditions are met: 11 | * 12 | * 1. Redistributions of source code must retain the above copyright notice, 13 | * this list of conditions and the following disclaimer. 14 | * 2. Redistributions in binary form must reproduce the above copyright notice, 15 | * this list of conditions and the following disclaimer in the documentation 16 | * and/or other materials provided with the distribution. 17 | * 18 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 19 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 21 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE 22 | * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 23 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 24 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 25 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 26 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 27 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 28 | * POSSIBILITY OF SUCH DAMAGE. 29 | * #L% 30 | */ 31 | package org.scijava.plugins.scripting.cpython; 32 | 33 | public class CPythonStartup { 34 | 35 | static { 36 | NarSystem.loadLibrary(); 37 | } 38 | 39 | /** 40 | * Spins up the Python thread. 41 | * 42 | * @param pythonCode the Python code to execute 43 | */ 44 | public native static void initializePythonThread(final String pythonCode); 45 | } 46 | -------------------------------------------------------------------------------- /src/main/resources/scripting-cpython.py: -------------------------------------------------------------------------------- 1 | '''scripting-cpython.py - Attach to Java CPython scripting 2 | 3 | scripting-cpython is licensed under the BSD license. See the 4 | accompanying file LICENSE for details. 5 | 6 | Copyright (C) 2009 - 2014 Board of Regents of the University of 7 | Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck 8 | Institute of Molecular Cell Biology and Genetics. 9 | All rights reserved. 10 | 11 | ''' 12 | 13 | import ast 14 | import inspect 15 | import javabridge as J 16 | import threading 17 | import logging 18 | import numpy as np 19 | import sys 20 | logger = logging.getLogger(__name__) 21 | 22 | def engine_requester(): 23 | J.attach() 24 | while True: 25 | try: 26 | msg = J.run_script( 27 | """importPackage(Packages.org.scijava.plugins.scripting.cpython); 28 | CPythonScriptEngine.engineRequestQueue.take();""") 29 | if logger.level <= logging.INFO: 30 | logger.info("Received engine request: %s", 31 | J.to_string(msg)) 32 | payload = J.get_collection_wrapper( 33 | J.run_script("msg.payload", dict(msg=msg))) 34 | if J.run_script( 35 | """importPackage(Packages.org.scijava.plugins.scripting.cpython); 36 | msg.command==CPythonScriptEngine.EngineCommands.NEW_ENGINE; 37 | """, dict(msg=msg)): 38 | do_new_engine(payload) 39 | elif J.run_script( 40 | """importPackage(Packages.org.scijava.plugins.scripting.cpython); 41 | msg.command==CPythonScriptEngine.EngineCommands.CLOSE_SERVICE; 42 | """, dict(msg=msg)): 43 | logger.info("Exiting script service thread in response to " 44 | "termination request") 45 | break 46 | else: 47 | J.run_script( 48 | """importPackage(Packages.org.scijava.plugins.scripting.cpython); 49 | var exception = new java.lang.RuntimeException( 50 | java.lang.String.format('Unknown command: %s', msg.command.toString())); 51 | var payload = new java.util.ArrayList(); 52 | payload.add(exception); 53 | var response = new CPythonScriptEngine.Message( 54 | CPythonScriptEngine.EngineCommands.EXCEPTION, payload); 55 | CPythonScriptEngine.engineResponseQueue.put(response); 56 | """) 57 | except: 58 | # To do: how to handle failure, probably from .take() 59 | # Guessing that someone has managed to interrupt our thread 60 | logger.warn("Exiting script service thread", exc_info=True) 61 | J.detach() 62 | 63 | def engine(q_request, q_response): 64 | logger.info("Starting script engine thread") 65 | J.attach() 66 | while True: 67 | try: 68 | msg = J.run_script( 69 | """importPackage(Packages.org.scijava.plugins.scripting.cpython); 70 | q_request.take();""", dict(q_request=q_request)) 71 | if logger.level <= logging.INFO: 72 | logger.info("Received engine request: %s", 73 | J.to_string(msg)) 74 | payload = J.get_collection_wrapper( 75 | J.run_script("msg.payload", dict(msg=msg))) 76 | if J.run_script( 77 | """importPackage(Packages.org.scijava.plugins.scripting.cpython); 78 | msg.command==CPythonScriptEngine.EngineCommands.EXECUTE; 79 | """, dict(msg=msg)): 80 | response = do_execute(payload) 81 | elif J.run_script( 82 | """importPackage(Packages.org.scijava.plugins.scripting.cpython); 83 | msg.command==CPythonScriptEngine.EngineCommands.EVALUATE; 84 | """, dict(msg=msg)): 85 | response = do_evaluate(payload) 86 | elif J.run_script( 87 | """importPackage(Packages.org.scijava.plugins.scripting.cpython); 88 | msg.command==CPythonScriptEngine.EngineCommands.CLOSE_ENGINE; 89 | """, dict(msg=msg)): 90 | logger.info("Exiting script engine thread after close request") 91 | break 92 | else: 93 | logger.warn( 94 | "Received unknown command: %s" % 95 | J.run_script("msg.command.toString()", dict(msg=msg))) 96 | response = J.run_script( 97 | """importPackage(Packages.org.scijava.plugins.scripting.cpython); 98 | var exception = new java.lang.RuntimeException( 99 | java.lang.String.format('Unknown command: %s', msg.command.toString())); 100 | var payload = new java.util.ArrayList(); 101 | payload.add(exception); 102 | new CPythonScriptEngine.Message( 103 | CPythonScriptEngine.EngineCommands.EXCEPTION, payload); 104 | """, dict(msg=msg)) 105 | J.run_script("q_response.put(response);", 106 | dict(q_response=q_response, 107 | response=response)) 108 | except: 109 | # To do: how to handle failure, probably from .take() 110 | # Guessing that someone has managed to interrupt our thread 111 | logger.warn("Exiting script engine thread", exc_info=True) 112 | J.detach() 113 | 114 | def do_new_engine(payload): 115 | '''Create a new engine thread 116 | 117 | payload: first member is request queue, second is response queue 118 | ''' 119 | logger.info("Creating new engine") 120 | thread = threading.Thread(target = engine, args=list(payload[:2]), 121 | name = "Scripting-CPythonEngine") 122 | thread.setDaemon(True) 123 | thread.start() 124 | J.run_script( 125 | """importPackage(Packages.org.scijava.plugins.scripting.cpython); 126 | var payload = new java.util.ArrayList(); 127 | var response = new CPythonScriptEngine.Message( 128 | CPythonScriptEngine.EngineCommands.NEW_ENGINE_RESULT, payload); 129 | CPythonScriptEngine.engineResponseQueue.put(response); 130 | """) 131 | 132 | def do_evaluate(payload): 133 | '''Evaluate a Python command 134 | 135 | payload: first member is Python command string, second is local context 136 | ''' 137 | logger.info("Evaluating script") 138 | filename = "scripting-cpython" 139 | try: 140 | command = J.to_string(payload[0]) 141 | context = context_to_locals(payload[1]) 142 | logger.debug("Script:\n%s" % command) 143 | # 144 | # OK, the game plan is a little difficult here: 145 | # 146 | # use AST to parse (see https://docs.python.org/2.7/library/ast.html) 147 | # The AST object's body is a list of statements. 148 | # If the last body element is an ast.Expr, then 149 | # we execute all of the statements except the last 150 | # and then we wrap the last as an ast.Expression 151 | # and evaluate it. 152 | # 153 | a = ast.parse(command) 154 | if isinstance(a.body[-1], ast.Expr): 155 | expr = a.body[-1] 156 | del a.body[-1] 157 | else: 158 | expr = a.parse("None").body[0] 159 | 160 | filename = context.get("javax.scripting.filename", 161 | "scripting-cpython") 162 | code = compile(a, filename, mode="exec") 163 | exec(code, __builtins__.__dict__, context) 164 | code = compile(ast.Expression(expr.value), filename, mode="eval") 165 | result = eval(code, __builtins__.__dict__, context) 166 | logger.debug("Script evaluated") 167 | return J.run_script( 168 | """importPackage(Packages.org.scijava.plugins.scripting.cpython); 169 | var payload = new java.util.ArrayList(); 170 | payload.add(result); 171 | new CPythonScriptEngine.Message( 172 | CPythonScriptEngine.EngineCommands.EVALUATE_RESULT, payload); 173 | """, dict(result=result)) 174 | except: 175 | logger.info("Exception caught during eval", exc_info=True) 176 | e_type, e, e_tb = sys.exc_info() 177 | 178 | return J.run_script( 179 | """ 180 | importPackage(Packages.org.scijava.plugins.scripting.cpython); 181 | var exception = new javax.script.ScriptException( 182 | java.lang.String.format('Python exception: %s', e), 183 | filename, line_number); 184 | var payload = new java.util.ArrayList(); 185 | payload.add(exception); 186 | new CPythonScriptEngine.Message( 187 | CPythonScriptEngine.EngineCommands.EXCEPTION, payload); 188 | """, dict(e=repr(e), filename = filename, 189 | line_number = e_tb.tb_lineno)) 190 | 191 | def context_to_locals(context): 192 | '''convert the local context as a Java map to a dictionary of locals''' 193 | d = { "JWrapper": JWrapper, 194 | "importClass": importClass } 195 | m = J.get_map_wrapper(context) 196 | for k in m: 197 | key = J.to_string(k) 198 | o = m[k] 199 | if isinstance(o, J.JB_Object): 200 | if J.is_instance_of(o, "java/lang/String"): 201 | d[key] = J.to_string(o) 202 | continue 203 | for class_name, method, signature in ( 204 | ("java/lang/Boolean", "booleanValue", "()Z"), 205 | ("java/lang/Byte", "byteValue", "()B"), 206 | ("java/lang/Integer", "intValue", "()I"), 207 | ("java/lang/Long", "longValue", "()L"), 208 | ("java/lang/Float", "floatValue", "()F"), 209 | ("java/lang/Double", "doubleValue", "()D")): 210 | if J.is_instance_of(o, class_name): 211 | d[key] = J.call(o, method, signature) 212 | break 213 | else: 214 | d[key] = JWrapper(o) 215 | else: 216 | d[key] = o 217 | 218 | return d 219 | 220 | class JWrapper(object): 221 | '''A class that wraps a Java object 222 | 223 | Usage: 224 | >>> a = JWrapper(javabridge.make_instance("java/util/ArrayList", "()V")) 225 | >>> a.add("Hello") 226 | >>> a.add("World") 227 | >>> a.size() 228 | 2 229 | ''' 230 | def __init__(self, o): 231 | '''Initialize the JWrapper with a Java object 232 | 233 | :param o: a Java object (class = JB_Object) 234 | ''' 235 | STATIC = J.get_static_field("java/lang/reflect/Modifier", "STATIC", "I") 236 | self.o = o 237 | self.class_wrapper = J.get_class_wrapper(o) 238 | env = J.get_env() 239 | methods = env.get_object_array_elements(self.class_wrapper.getMethods()) 240 | self.methods = {} 241 | for jmethod in methods: 242 | if (J.call(jmethod, "getModifiers", "()I") & STATIC) == STATIC: 243 | continue 244 | method = J.get_method_wrapper(jmethod) 245 | name = method.getName() 246 | if name not in self.methods: 247 | self.methods[name] = [] 248 | fn = lambda naame=name: lambda *args: self.__call(naame, *args) 249 | fn = fn() 250 | fn.__doc__ = J.to_string(jmethod) 251 | setattr(self, name, fn) 252 | else: 253 | fn = getattr(self, name) 254 | fn.__doc__ = fn.__doc__ +"\n"+J.to_string(jmethod) 255 | self.methods[name].append(method) 256 | jfields = self.class_wrapper.getFields() 257 | fields = env.get_object_array_elements(jfields) 258 | 259 | def __getattr__(self, name): 260 | try: 261 | jfield = self.klass.getField(name) 262 | except: 263 | raise AttributeError() 264 | 265 | STATIC = J.get_static_field("java/lang/reflect/Modifier", "STATIC", "I") 266 | if (J.call(jfield, "getModifiers", "()I") & STATIC) == STATIC: 267 | raise AttributeError() 268 | klass = J.call(jfield, "getType", "()Ljava/lang/Class;") 269 | result = J.get_field(self.o, name, sig(klass)) 270 | if isinstance(result, J.JB_Object): 271 | result = JWrapper(result) 272 | return result 273 | 274 | def __setattr__(self, name, value): 275 | try: 276 | jfield = self.klass.getField(name) 277 | except: 278 | object.__setattr__(self, name, value) 279 | return 280 | 281 | STATIC = J.get_static_field("java/lang/reflect/Modifier", "STATIC", "I") 282 | if (J.call(jfield, "getModifiers", "()I") & STATIC) == STATIC: 283 | raise AttributeError() 284 | klass = J.call(jfield, "getType", "()Ljava/lang/Class;") 285 | result = J.set_field(self.o, name, sig(klass), value) 286 | 287 | def __call(self, method_name, *args): 288 | '''Call the appropriate overloaded method with the given name 289 | 290 | :param method_name: the name of the method to call 291 | :param *args: the arguments to the method, which are used to 292 | disambiguate between similarly named methods 293 | ''' 294 | env = J.get_env() 295 | last_e = None 296 | for method in self.methods[method_name]: 297 | params = env.get_object_array_elements(method.getParameterTypes()) 298 | is_var_args = J.call(method.o, "isVarArgs", "()Z") 299 | if len(args) < len(params) - (1 if is_var_args else 0): 300 | continue 301 | if len(args) > len(params) and not is_var_args: 302 | continue 303 | if is_var_args: 304 | pm1 = len(params)-1 305 | args1 = args[:pm1] + [args[pm1:]] 306 | else: 307 | args1 = args 308 | try: 309 | cargs = [cast(o, klass) for o, klass in zip(args1, params)] 310 | except: 311 | last_e = sys.exc_info()[1] 312 | continue 313 | rtype = J.call(method.o, "getReturnType", "()Ljava/lang/Class;") 314 | args_sig = "".join(map(sig, params)) 315 | rsig = sig(rtype) 316 | msig = "(%s)%s" % (args_sig, rsig) 317 | result = J.call(self.o, method_name, msig, *cargs) 318 | if isinstance(result, J.JB_Object): 319 | result = JWrapper(result) 320 | return result 321 | raise TypeError("No matching method found for %s" % method_name) 322 | 323 | def __repr__(self): 324 | classname = J.call(J.call(self.o, "getClass", "()Ljava/lang/Class;"), 325 | "getName", "()Ljava/lang/String;") 326 | return "Instance of %s: %s" % (classname, J.to_string(self.o)) 327 | 328 | def __str__(self): 329 | return J.to_string(self.o) 330 | 331 | class JClassWrapper(object): 332 | '''Wrapper for a class 333 | 334 | >>> Integer = JClassWrapper("java.lang.Integer") 335 | >>> Integer.MAX_VALUE 336 | 2147483647 337 | ''' 338 | def __init__(self, class_name): 339 | '''Initialize to wrap a class name 340 | 341 | :param class_name: name of class in dotted form, e.g. java.lang.Integer 342 | ''' 343 | STATIC = J.get_static_field("java/lang/reflect/Modifier", "STATIC", "I") 344 | self.cname = class_name.replace(".", "/") 345 | self.klass = J.get_class_wrapper(J.class_for_name(class_name), True) 346 | self.static_methods = {} 347 | methods = env.get_object_array_elements(self.klass.getMethods()) 348 | self.methods = {} 349 | for jmethod in methods: 350 | if (J.call(jmethod, "getModifiers", "()I") & STATIC) != STATIC: 351 | continue 352 | method = J.get_method_wrapper(jmethod) 353 | name = method.getName() 354 | if name not in self.methods: 355 | self.methods[name] = [] 356 | fn = lambda naame=name: lambda *args: self.__call_static(naame, *args) 357 | fn = fn() 358 | fn.__doc__ = J.to_string(jmethod) 359 | setattr(self, name, fn) 360 | else: 361 | fn = getattr(self, name) 362 | fn.__doc__ = fn.__doc__ +"\n"+J.to_string(jmethod) 363 | self.methods[name].append(method) 364 | 365 | def __getattr__(self, name): 366 | try: 367 | jfield = self.klass.getField(name) 368 | except: 369 | raise AttributeError("Could not find field %s" % name) 370 | 371 | STATIC = J.get_static_field("java/lang/reflect/Modifier", "STATIC", "I") 372 | if (J.call(jfield, "getModifiers", "()I") & STATIC) != STATIC: 373 | raise AttributeError("Field %s is not static" % name) 374 | klass = J.call(jfield, "getType", "()Ljava/lang/Class;") 375 | result = J.get_static_field(self.cname, name, sig(klass)) 376 | if isinstance(result, J.JB_Object): 377 | result = JWrapper(result) 378 | return result 379 | 380 | def __setattr__(self, name, value): 381 | try: 382 | jfield = self.klass.getField(name) 383 | except: 384 | return object.__setattr__(self, name, value) 385 | 386 | STATIC = J.get_static_field("java/lang/reflect/Modifier", "STATIC", "I") 387 | if (J.call(jfield, "getModifiers", "()I") & STATIC) != STATIC: 388 | raise AttributeError() 389 | klass = J.call(jfield, "getType", "()Ljava/lang/Class;") 390 | result = J.set_static_field(self.cname, name, sig(klass), value) 391 | 392 | def __call_static(self, method_name, *args): 393 | '''Call the appropriate overloaded method with the given name 394 | 395 | :param method_name: the name of the method to call 396 | :param *args: the arguments to the method, which are used to 397 | disambiguate between similarly named methods 398 | ''' 399 | env = J.get_env() 400 | last_e = None 401 | for method in self.methods[method_name]: 402 | params = env.get_object_array_elements(method.getParameterTypes()) 403 | is_var_args = J.call(method.o, "isVarArgs", "()Z") 404 | if len(args) < len(params) - (1 if is_var_args else 0): 405 | continue 406 | if len(args) > len(params) and not is_var_args: 407 | continue 408 | if is_var_args: 409 | pm1 = len(params)-1 410 | args1 = args[:pm1] + [args[pm1:]] 411 | else: 412 | args1 = args 413 | try: 414 | cargs = [cast(o, klass) for o, klass in zip(args1, params)] 415 | except: 416 | last_e = sys.exc_info()[1] 417 | continue 418 | rtype = J.call(method.o, "getReturnType", "()Ljava/lang/Class;") 419 | args_sig = "".join(map(sig, params)) 420 | rsig = sig(rtype) 421 | msig = "(%s)%s" % (args_sig, rsig) 422 | result = J.static_call(self.cname, method_name, msig, *cargs) 423 | if isinstance(result, J.JB_Object): 424 | result = JWrapper(result) 425 | return result 426 | raise TypeError("No matching method found for %s" % method_name) 427 | 428 | def __call__(self, *args): 429 | '''Constructors''' 430 | env = J.get_env() 431 | jconstructors = self.klass.getConstructors() 432 | for jconstructor in env.get_object_array_elements(jconstructors): 433 | constructor = J.get_constructor_wrapper(jconstructor) 434 | params = env.get_object_array_elements( 435 | constructor.getParameterTypes()) 436 | is_var_args = J.call(constructor.o, "isVarArgs", "()Z") 437 | if len(args) < len(params) - (1 if is_var_args else 0): 438 | continue 439 | if len(args) > len(params) and not is_var_args: 440 | continue 441 | if is_var_args: 442 | pm1 = len(params)-1 443 | args1 = args[:pm1] + [args[pm1:]] 444 | else: 445 | args1 = args 446 | try: 447 | cargs = [cast(o, klass) for o, klass in zip(args1, params)] 448 | except: 449 | last_e = sys.exc_info()[1] 450 | continue 451 | args_sig = "".join(map(sig, params)) 452 | msig = "(%s)V" % (args_sig) 453 | result = J.make_instance(self.cname, msig, *cargs) 454 | result = JWrapper(result) 455 | return result 456 | raise TypeError("No matching constructor found") 457 | 458 | def importClass(class_name, import_name = None): 459 | '''Import a wrapped class into the global context 460 | 461 | :param class_name: a dotted class name such as java.lang.String 462 | :param import_name: if defined, use this name instead of the class's name 463 | ''' 464 | if import_name is None: 465 | if "." in class_name: 466 | import_name = class_name.rsplit(".", 1)[1] 467 | else: 468 | import_name = class_name 469 | frame = inspect.currentframe(1) 470 | frame.f_locals[import_name] = JClassWrapper(class_name) 471 | 472 | def sig(klass): 473 | '''Return the JNI signature for a class''' 474 | name = J.call(klass, "getName", "()Ljava/lang/String;") 475 | if not (J.call(klass, "isPrimitive", "()Z") or 476 | J.call(klass, "isArray", "()Z")): 477 | name = "L%s;" % name 478 | if name == 'void': 479 | return "V" 480 | if name == 'int': 481 | return "I" 482 | if name == 'byte': 483 | return "B" 484 | if name == 'boolean': 485 | return "Z" 486 | if name == 'long': 487 | return "J" 488 | if name == 'float': 489 | return "F" 490 | if name == 'double': 491 | return "D" 492 | if name == 'char': 493 | return "C" 494 | if name == 'short': 495 | return "S" 496 | return name.replace(".", "/") 497 | 498 | def cast(o, klass): 499 | '''Cast the given object to the given class 500 | 501 | :param o: either a Python object or Java object to be cast 502 | :param klass: a java.lang.Class indicating the target class 503 | 504 | raises a TypeError if the object can't be cast. 505 | ''' 506 | is_primitive = J.call(klass, "isPrimitive", "()Z") 507 | csig = sig(klass) 508 | if o is None: 509 | if not is_primitive: 510 | return None 511 | else: 512 | raise TypeError("Can't cast None to a primitive type") 513 | 514 | if isinstance(o, J.JB_Object): 515 | if J.call(klass, "isInstance", "(Ljava/lang/Object;)Z", o): 516 | return o 517 | classname = J.run_script("o.getClass().getCanonicalName()", dict(o=o)) 518 | klassname = J.run_script("klass.getCanonicalName()", dict(klass=klass)) 519 | raise TypeError("Object of class %s cannot be cast to %s", 520 | classname, klassname) 521 | elif hasattr(o, "o"): 522 | return cast(o.o, klass) 523 | elif not np.isscalar(o): 524 | component_type = J.call(klass, "getComponentType", "()Ljava/lang/Class;") 525 | if component_type is None: 526 | raise TypeError("Argument must not be a sequence") 527 | if len(o) > 0: 528 | # Test if an element can be cast to the array type 529 | cast(o[0], component_type) 530 | return J.get_nice_arg(o, csig) 531 | elif is_primitive or csig in ('Ljava/lang/String;', 'Ljava/lang/Object;'): 532 | return J.get_nice_arg(o, csig) 533 | raise TypeError("Failed to convert argument to %s" % csig) 534 | 535 | 536 | 537 | def do_execute(payload): 538 | '''Execute a Python command 539 | 540 | payload: first member is Python command string, second is local context 541 | ''' 542 | logger.info("Executing script") 543 | try: 544 | command = J.to_string(payload[0]) 545 | context = context_to_locals(payload[1]) 546 | logger.debug("Script:\n%s" % command) 547 | exec(command, __builtins__.__dict__, context) 548 | logger.debug("Script evaluated") 549 | return J.run_script( 550 | """importPackage(Packages.org.scijava.plugins.scripting.cpython); 551 | var payload = new java.util.ArrayList(); 552 | new CPythonScriptEngine.Message( 553 | CPythonScriptEngine.EngineCommands.EXECUTION, payload); 554 | """) 555 | except: 556 | logger.info("Exception caught during execute", exc_info=True) 557 | return J.run_script( 558 | """importPackage(Packages.org.scijava.plugins.scripting.cpython); 559 | var exception = new java.lang.RuntimeException( 560 | java.lang.String.format('Python exception: %s', e)); 561 | var payload = new java.util.ArrayList(); 562 | payload.add(exception); 563 | new CPythonScriptEngine.Message( 564 | CPythonScriptEngine.EngineCommands.EXCEPTION, payload); 565 | """, dict(e=repr(sys.exc_info()[1]))) 566 | 567 | logger.info("Running scripting-cpython script") 568 | thread = threading.Thread(target=engine_requester, name="Scripting-CPython Engine Requester") 569 | thread.setDaemon(True) 570 | thread.start() 571 | -------------------------------------------------------------------------------- /src/test/java/org/scijava/plugins/scripting/cpython/CPythonTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * #%L 3 | * JSR-223-compliant Python scripting language plugin linking to CPython 4 | * %% 5 | * Copyright (C) 2014 Board of Regents of the University of 6 | * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck 7 | * Institute of Molecular Cell Biology and Genetics. 8 | * %% 9 | * Redistribution and use in source and binary forms, with or without 10 | * modification, are permitted provided that the following conditions are met: 11 | * 12 | * 1. Redistributions of source code must retain the above copyright notice, 13 | * this list of conditions and the following disclaimer. 14 | * 2. Redistributions in binary form must reproduce the above copyright notice, 15 | * this list of conditions and the following disclaimer in the documentation 16 | * and/or other materials provided with the distribution. 17 | * 18 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 19 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 21 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE 22 | * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 23 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 24 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 25 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 26 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 27 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 28 | * POSSIBILITY OF SUCH DAMAGE. 29 | * #L% 30 | */ 31 | package org.scijava.plugins.scripting.cpython; 32 | 33 | import org.junit.Test; 34 | 35 | public class CPythonTest { 36 | 37 | @Test 38 | public void initializeTest() { 39 | CPythonStartup.initializePythonThread("print 'Hello, Lee!'"); 40 | } 41 | } 42 | --------------------------------------------------------------------------------