package jenkins.python;
import java.io.File;
import java.lang.reflect.Method;
import org.python.util.PythonInterpreter;
import org.python.core.*;
/**
* Executes functions inside Jython interpreter
*/
public class PythonExecutor {
private PythonInterpreter pinterp;
// callId guarantees unique attributes' names for every function call inside Jython interpreter
private int callId;
private boolean[] funcFlags;
private Object extension;
public PythonExecutor(Object extension) throws PythonWrapperError {
String scriptPath = getScriptPath(extension);
pinterp = new PythonInterpreter();
pinterp.exec("import sys");
String modulePath = new File(scriptPath).getParent();
modulePath = modulePath.replace("\\", "\\\\");
modulePath = modulePath.replace("'", "\\'");
String cmdPath = "sys.path.append('" + modulePath + "')";
pinterp.exec(cmdPath);
pinterp.execfile(scriptPath);
pinterp.set("extension", extension);
callId = 0;
this.extension = extension;
if (hasFunction("init_plugin", 0)) {
pinterp.exec("init_plugin()");
}
}
private synchronized int getCallId() {
int actCallId = callId;
// increase call id for the next use
callId++;
if (callId < 0) {
callId = 0;
}
return actCallId;
}
/**
* Determines if a function with the specified id is implemented in the Python script.
*/
public boolean isImplemented(int id) {
return funcFlags[id];
}
/**
* Search for all functions from the given array in the Python script and register
* availability flags for them.
*/
public void registerFunctions(String[] functions, int[] argsCount) {
// init funcFlags with false values
funcFlags = new boolean[functions.length];
for (int i = 0; i < functions.length; i++) {
if (hasFunction(functions[i], argsCount[i])) {
// a function exists, mark it with the true flag
funcFlags[i] = true;
}
}
}
/**
* Check if all abstract methods are implemented either in Java class or in Python script
* @throws PythonWrapperError if any of methods has no implementation.
*/
public void checkAbstrMethods(String[] jMethods, String[] pFuncs, Class<?>[][] argTypes) throws PythonWrapperError {
Method[] methods = extension.getClass().getDeclaredMethods();
for (int i = 0; i < jMethods.length; i++) {
boolean found = false;
if (hasFunction(pFuncs[i], argTypes[i].length)) {
// great, python implementation is presented
found = true;
}
else {
for (int j = 0; j < methods.length; j++) {
Method m = methods[j];
if (m.getName().equals(jMethods[i])) {
Class<?>[] paramTypes = m.getParameterTypes();
if (paramTypes.length != argTypes[i].length) {
continue;
}
if (paramTypes.length == 0) {
// this java method override the abstract method
found = true;
break;
}
for (int k = 0; k < paramTypes.length; k++) {
if (argTypes[i][k] == null) {
// this is for parametric types, do nothing
}
else if (paramTypes[k] != argTypes[i][k]) {
break;
}
if (k == paramTypes.length - 1) {
// this java method override the abstract method
found = true;
}
}
}
}
}
if (!found) {
// abstract method is not implemented, let's inform a developer!
throw new PythonWrapperError("The abstract method '" + jMethods[i] + "' of this extension " +
"has to be implemented in either " +
"Python (" + pFuncs[i] + "(" + new Integer(argTypes[i].length) + "))" +
" or " +
"Java (" + jMethods[i] + "(" + new Integer(argTypes[i].length) + ")).");
}
}
// great, all methods are implemented!
}
/**
* Determines if there is a function with the given name and the correct number of arguments
* in the loaded script.
*/
public boolean hasFunction(String name, int argsCount) {
PyStringMap locals = (PyStringMap)pinterp.getLocals();
if (locals.has_key(name)) {
// great, there is some variable with this name
PyObject obj = locals.get(new PyString(name));
if (obj.getClass() == PyFunction.class) {
// great, it's a function
PyFunction fnc = (PyFunction)obj;
int aCount = ((PyTableCode)fnc.func_code).co_argcount;
if (aCount == argsCount) {
// great, it accepts correct number of arguments
return true;
}
boolean varargs = ((PyTableCode)fnc.func_code).varargs;
if (aCount < argsCount && varargs) {
// great, it is variable arguments function
return true;
}
}
}
return false;
}
/**
* Finds a correct file path of a python implementation for this object.
*/
private String getScriptPath(Object obj) throws PythonWrapperError {
String className = obj.getClass().getName();
File scriptFile;
String classFolderPath = obj.getClass().getProtectionDomain()
.getCodeSource().getLocation().getPath();
classFolderPath = classFolderPath.replace("%20", " ");
File classFolder = new File(classFolderPath);
if (classFolder.getPath().endsWith(".jar")) {
// normal mode (plugin was properly installed)
JARUnpacker.unpackPythonFiles(classFolder);
scriptFile = new File(classFolder.getParentFile(), "python");
}
else {
// "mvn hpi:run" mode (plugin debug)
scriptFile = new File(classFolder, "python");
}
scriptFile = new File(scriptFile, NameConvertor.javaClassToPythonFile(className));
return scriptFile.getPath();
}
/**
* Call the function inside Jython interpreter and return PyObject
*/
private PyObject execPythonGeneric(String function, Object ... params) {
int myCallId = getCallId();
// prepare function call string
String paramName;
String execStr = function + "(";
for (int i = 0; i < params.length; i++) {
paramName = "_" + function + "_" + (new Integer(myCallId)).toString() + "_" + (new Integer(i)).toString();
pinterp.set(paramName, params[i]);
execStr += paramName;
if (i < params.length-1) {
execStr += ", ";
}
}
execStr += ")";
// call function inside Jython interpreter
PyObject obj = pinterp.eval(execStr);
// delete params from Jython interpreter namespace
for (int i = 0; i < params.length; i++) {
paramName = "_" + function + "_" + (new Integer(myCallId)).toString() + "_" + (new Integer(i)).toString();
pinterp.exec("del " + paramName);
}
return obj;
}
/**
* Call the function inside Jython interpreter and return Java Object
*/
public Object execPython(String function, Object ... params) {
PyObject obj = execPythonGeneric(function, params);
return DataConvertor.toObject(obj, Object.class);
}
/**
* Call the function inside Jython interpreter
*/
public void execPythonVoid(String function, Object ... params) {
execPythonGeneric(function, params);
}
/**
* Call the function inside Jython interpreter and return bool value
*/
public boolean execPythonBool(String function, Object ... params) {
PyObject obj = execPythonGeneric(function, params);
return DataConvertor.toBool(obj);
}
/**
* Call the function inside Jython interpreter and return double value
*/
public double execPythonDouble(String function, Object ... params) {
PyObject obj = execPythonGeneric(function, params);
return DataConvertor.toDouble(obj);
}
/**
* Call the function inside Jython interpreter and return float value
*/
public float execPythonFloat(String function, Object ... params) {
PyObject obj = execPythonGeneric(function, params);
return DataConvertor.toFloat(obj);
}
/**
* Call the function inside Jython interpreter and return long value
*/
public long execPythonLong(String function, Object ... params) {
PyObject obj = execPythonGeneric(function, params);
return DataConvertor.toLong(obj);
}
/**
* Call the function inside Jython interpreter and return integer value
*/
public int execPythonInt(String function, Object ... params) {
PyObject obj = execPythonGeneric(function, params);
return DataConvertor.toInt(obj);
}
/**
* Call the function inside Jython interpreter and return short value
*/
public short execPythonShort(String function, Object ... params) {
PyObject obj = execPythonGeneric(function, params);
return DataConvertor.toShort(obj);
}
/**
* Call the function inside Jython interpreter and return byte value
*/
public byte execPythonByte(String function, Object ... params) {
PyObject obj = execPythonGeneric(function, params);
return DataConvertor.toByte(obj);
}
/**
* Call the function inside Jython interpreter and return char value
*/
public char execPythonChar(String function, Object ... params) {
PyObject obj = execPythonGeneric(function, params);
return DataConvertor.toChar(obj);
}
}