/*
* Copyright (c) 2010-2016, Sikuli.org, sikulix.com
* Released under the MIT License.
*
*/
package org.sikuli.util;
import java.io.File;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import org.sikuli.basics.Debug;
import org.sikuli.basics.FileManager;
import org.sikuli.basics.Settings;
import org.sikuli.script.ImagePath;
import org.sikuli.script.RunTime;
import org.sikuli.script.Sikulix;
public class JythonHelper implements JLangHelperInterface {
static RunTime runTime = RunTime.get();
//<editor-fold defaultstate="collapsed" desc="new logging concept">
private static final String me = "JythonSupport: ";
private static int lvl = 3;
public void log(int level, String message, Object... args) {
Debug.logx(level, me + message, args);
}
private void logp(String message, Object... args) {
Debug.logx(-3, message, args);
}
private void logp(int level, String message, Object... args) {
if (level <= Debug.getDebugLevel()) {
logp(message, args);
}
}
public void terminate(int retVal, String msg, Object... args) {
runTime.terminate(retVal, me + msg, args);
}
//</editor-fold>
static JythonHelper instance = null;
static Object interpreter = null;
List<String> sysPath = new ArrayList<String>();
List<String> sysArgv = new ArrayList<String>();
int nPathAdded = 0;
int nPathSaved = -1;
static Class[] nc = new Class[0];
static Class[] nc1 = new Class[1];
static Class cInterpreter = null;
static Class cPyException = null;
static Class cList = null;
static Class cPy = null;
static Class cPyFunction = null;
static Class cPyMethod = null;
static Class cPyInstance = null;
static Class cPyObject = null;
static Class cPyString = null;
static Method mLen, mGet, mSet, mAdd, mRemove, mClear;
static Method mGetSystemState, mExec, mExecfile;
static Field PI_path;
private JythonHelper() {
}
public static JythonHelper get() {
if (instance == null) {
instance = new JythonHelper();
instance.log(lvl, "init: starting");
try {
cInterpreter = Class.forName("org.python.util.PythonInterpreter");
} catch (Exception ex) {
String sJython = new File(runTime.SikuliJython).getName();
File fJython = new File(runTime.fSikulixDownloadsGeneric, sJython);
instance.log(lvl, "trying to use setup downloaded Jython:\n%s", fJython.getAbsolutePath());
if (fJython.exists()) {
runTime.addToClasspath(fJython.getAbsolutePath());
} else {
instance.log(-1, "Not possible to get a Jython on to the classpath!");
cInterpreter = null;
}
}
try {
cInterpreter = Class.forName("org.python.util.PythonInterpreter");
mGetSystemState = cInterpreter.getMethod("getSystemState", nc);
mExec = cInterpreter.getMethod("exec", new Class[]{String.class});
mExecfile = cInterpreter.getMethod("execfile", new Class[]{String.class});
Constructor PI_new = cInterpreter.getConstructor(nc);
interpreter = PI_new.newInstance(null);
cPyException = Class.forName("org.python.core.PyException");
cList = Class.forName("org.python.core.PyList");
cPy = Class.forName("org.python.core.Py");
cPyFunction = Class.forName("org.python.core.PyFunction");
cPyMethod = Class.forName("org.python.core.PyMethod");
cPyInstance = Class.forName("org.python.core.PyInstance");
cPyObject = Class.forName("org.python.core.PyObject");
cPyString = Class.forName("org.python.core.PyString");
mLen = cList.getMethod("__len__", nc);
mClear = cList.getMethod("clear", nc);
mGet = cList.getMethod("get", new Class[]{int.class});
mSet = cList.getMethod("set", new Class[]{int.class, Object.class});
mAdd = cList.getMethod("add", new Class[]{Object.class});
mRemove = cList.getMethod("remove", new Class[]{int.class});
} catch (Exception ex) {
cInterpreter = null;
}
instance.log(lvl, "init: success");
}
if (cInterpreter == null) {
instance.runTime.terminate(1, "JythonHelper: no Jython on classpath");
}
runTime.isJythonReady = true;
return instance;
}
private void noOp() {
} // for debugging as breakpoint
class PyException {
Object inst = null;
Field fType = null;
Field fValue = null;
Field fTrBack = null;
public PyException(Object i) {
inst = i;
cPyException.cast(inst);
try {
fType = cPyException.getField("type");
fValue = cPyException.getField("value");
fTrBack = cPyException.getField("traceback");
} catch (Exception ex) {
noOp();
}
}
public int isTypeExit() {
try {
if (fType.get(inst).toString().contains("SystemExit")) {
return Integer.parseInt(fValue.get(inst).toString());
}
} catch (Exception ex) {
return -999;
}
return -1;
}
}
class PyInstance {
Object inst = null;
Method mGetAttr = null;
Method mInvoke = null;
public PyInstance(Object i) {
inst = i;
cPyInstance.cast(inst);
try {
mGetAttr = cPyInstance.getMethod("__getattr__", String.class);
mInvoke = cPyInstance.getMethod("invoke", String.class, cPyObject);
} catch (Exception ex) {
noOp();
}
}
public Object get() {
return inst;
}
Object __getattr__(String mName) {
if (mGetAttr == null) {
return null;
}
Object method = null;
try {
method = mGetAttr.invoke(inst, mName);
} catch (Exception ex) {
}
return method;
}
public void invoke(String mName, Object arg) {
if (mInvoke != null) {
try {
mInvoke.invoke(inst, mName, arg);
} catch (Exception ex) {
noOp();
}
}
}
}
class PyFunction {
public String __name__;
Object func = null;
Method mCall = null;
Method mCall1 = null;
public PyFunction(Object f) {
func = f;
try {
cPyFunction.cast(func);
mCall = cPyFunction.getMethod("__call__");
mCall1 = cPyFunction.getMethod("__call__", cPyObject);
} catch (Exception ex) {
func = null;
}
if (func == null) {
try {
func = f;
cPyMethod.cast(func);
mCall = cPyMethod.getMethod("__call__");
mCall1 = cPyMethod.getMethod("__call__", cPyObject);
} catch (Exception ex) {
func = null;
}
}
}
void __call__(Object arg) {
if (mCall1 != null) {
try {
mCall1.invoke(func, arg);
} catch (Exception ex) {
}
}
}
void __call__() {
if (mCall != null) {
try {
mCall.invoke(func);
} catch (Exception ex) {
}
}
}
}
class Py {
Method mJava2py = null;
public Py() {
try {
mJava2py = cPy.getMethod("java2py", Object.class);
} catch (Exception ex) {
noOp();
}
}
Object java2py(Object arg) {
if (mJava2py == null) {
return null;
}
Object pyObject = null;
try {
pyObject = mJava2py.invoke(null, arg);
} catch (Exception ex) {
noOp();
}
return pyObject;
}
}
class PyString {
String aString = "";
Object pyString = null;
public PyString(String s) {
aString = s;
try {
pyString = cPyString.getConstructor(String.class).newInstance(aString);
} catch (Exception ex) {
}
}
public Object get() {
return pyString;
}
}
public boolean exec(String code) {
try {
mExec.invoke(interpreter, code);
} catch (Exception ex) {
PyException pex = new PyException(ex.getCause());
if (pex.isTypeExit() < 0) {
log(-1, "exec: returns:\n%s", ex.getCause());
}
return false;
}
return true;
}
public int execfile(String fpScript) {
int retval = -999;
try {
mExecfile.invoke(interpreter, fpScript);
} catch (Exception ex) {
PyException pex = new PyException(ex.getCause());
if ((retval = pex.isTypeExit()) < 0) {
log(-1, "execFile: returns:\n%s", ex.getCause());
}
}
return retval;
}
//TODO check signature (instance method)
public boolean checkCallback(Object[] args) {
PyInstance inst = new PyInstance(args[0]);
String mName = (String) args[1];
Object method = inst.__getattr__(mName);
if (method == null || !method.getClass().getName().contains("PyMethod")) {
log(-100, "checkCallback: Object: %s, Method not found: %s", inst, mName);
return false;
}
return true;
}
public boolean runLoggerCallback(Object[] args) {
PyInstance inst = new PyInstance(args[0]);
String mName = (String) args[1];
String msg = (String) args[2];
Object method = inst.__getattr__(mName);
if (method == null || !method.getClass().getName().contains("PyMethod")) {
log(-100, "runLoggerCallback: Object: %s, Method not found: %s", inst, mName);
return false;
}
try {
PyString pmsg = new PyString(msg);
inst.invoke(mName, pmsg.get());
} catch (Exception ex) {
log(-100, "runLoggerCallback: invoke: %s", ex.getMessage());
return false;
}
return true;
}
@Override
public boolean runObserveCallback(Object[] args) {
PyFunction func = new PyFunction(args[0]);
boolean success = true;
try {
func.__call__(new Py().java2py(args[1]));
} catch (Exception ex) {
// if (!"<lambda>".equals(func.__name__)) {
if (!func.toString().contains("<lambda>")) {
log(-1, "runObserveCallback: jython invoke: %s", ex.getMessage());
return false;
}
success = false;
}
if (success) {
return true;
}
try {
func.__call__();
} catch (Exception ex) {
log(-1, "runObserveCallback: jython invoke <lambda>: %s", ex.getMessage());
return false;
}
return true;
}
//TODO implement generalized callback
public boolean runCallback(Object[] args) {
PyInstance inst = (PyInstance) args[0];
String mName = (String) args[1];
Object method = inst.__getattr__(mName);
if (method == null || !method.getClass().getName().contains("PyMethod")) {
log(-1, "runCallback: Object: %s, Method not found: %s", inst, mName);
return false;
}
try {
PyString pmsg = new PyString("not yet supported");
inst.invoke(mName, pmsg.get());
} catch (Exception ex) {
log(-1, "runCallback: invoke: %s", ex.getMessage());
return false;
}
return true;
}
public static JythonHelper set(Object ip) {
JythonHelper.get();
interpreter = ip;
return instance;
}
public boolean prepareRobot() {
if (runTime.isRunningFromJar()) {
File fLibRobot = new File(runTime.fSikulixLib, "robot");
if (!fLibRobot.exists()) {
log(-1, "prepareRobot: not available: %s", fLibRobot);
Sikulix.terminate(1);
}
if (!hasSysPath(runTime.fSikulixLib.getAbsolutePath())) {
insertSysPath(runTime.fSikulixLib);
}
}
if (!hasSysPath(new File(Settings.BundlePath).getParent())) {
appendSysPath(new File(Settings.BundlePath).getParent());
}
exec("import robot");
return true;
}
public String load(String fpJarOrFolder) {
//##
//# loads a Sikuli extension (.jar) from
//# 1. user's sikuli data path
//# 2. bundle path
//#
//def load(jar):
// def _load(abspath):
// if os.path.exists(abspath):
// if not abspath in sys.path:
// sys.path.append(abspath)
// return True
// return False
//
// if JythonHelper.load(jar):
// return True
//
// if _load(jar):
// return True
// path = getBundlePath()
// if path:
// jarInBundle = os.path.join(path, jar)
// if _load(jarInBundle):
// return True
// path = ExtensionManager.getInstance().getLoadPath(jar)
// if path and _load(path):
// return True
// return False
log(lvl, "load: to be loaded:\n%s", fpJarOrFolder);
if (!fpJarOrFolder.endsWith(".jar")) {
fpJarOrFolder += ".jar";
}
String fpBundle = ImagePath.getBundlePath();
File fJar = new File(FileManager.normalizeAbsolute(fpJarOrFolder, false));
if (!fJar.exists()) {
fJar = new File(fpBundle, fpJarOrFolder);
fJar = new File(FileManager.normalizeAbsolute(fJar.getPath(), false));
if (!fJar.exists()) {
fJar = new File(runTime.fSikulixExtensions, fpJarOrFolder);
if (!fJar.exists()) {
fJar = new File(runTime.fSikulixLib, fpJarOrFolder);
if (!fJar.exists()) {
fJar = null;
}
}
}
}
if (fJar != null) {
if (runTime.addToClasspath(fJar.getPath())) {
if (!hasSysPath(fJar.getPath())) {
insertSysPath(fJar);
}
} else {
log(-1, "load: not possible");
}
} else {
log(-1, "load: could not be found - even not in bundle nor in Lib nor in Extensions");
}
if (fJar == null) {
return null;
}
return fJar.getAbsolutePath();
}
private long lastRun = 0;
private List<File> importedScripts = new ArrayList<File>();
String name = "";
public void reloadImported() {
if (lastRun > 0) {
for (File fMod : importedScripts) {
name = getPyName(fMod);
if (new File(fMod, name + ".py").lastModified() > lastRun) {
log(lvl, "reload: %s", fMod);
get().exec("reload(" + name + ")");
};
}
}
lastRun = new Date().getTime();
}
private String getPyName(File fMod) {
String ending = ".sikuli";
String name = fMod.getName();
if (name.endsWith(ending)) {
name = name.substring(0, name.length() - ending.length());
}
return name;
}
public String findModule(String modName, Object packPath, Object sysPath) {
if (modName.endsWith(".*")) {
log(lvl + 1, "findModule: %s", modName);
return null;
}
if (packPath != null) {
log(lvl + 1, "findModule: in pack: %s (%s)", modName, packPath);
return null;
}
int nDot = modName.lastIndexOf(".");
String modNameFull = modName;
if (nDot > -1) {
modName = modName.substring(nDot + 1);
}
String fpBundle = ImagePath.getBundlePath();
File fParentBundle = null;
File fModule = null;
if (fpBundle != null) {
fParentBundle = new File(fpBundle).getParentFile();
fModule = existsModule(modName, fParentBundle);
}
if (fModule == null) {
fModule = existsSysPathModule(modName);
if (fModule == null) {
return null;
}
}
log(lvl + 1, "findModule: final: %s [%s]", fModule.getName(), fModule.getParent());
if (fModule.getName().endsWith(".sikuli")) {
importedScripts.add(fModule);
return fModule.getAbsolutePath();
}
return null;
}
public String loadModulePrepare(String modName, String modPath) {
log(lvl + 1, "loadModulePrepare: %s in %s", modName, modPath);
int nDot = modName.lastIndexOf(".");
if (nDot > -1) {
modName = modName.substring(nDot + 1);
}
addSysPath(modPath);
if (modPath.endsWith(".sikuli")) {
ImagePath.add(modPath);
}
return modName;
}
private File existsModule(String mName, File fFolder) {
if (mName.endsWith(".sikuli") || mName.endsWith(".py")) {
return null;
}
File fSikuli = new File(fFolder, mName + ".sikuli");
if (fSikuli.exists()) {
return fSikuli;
}
File fPython = new File(fFolder, mName + ".py");
if (fPython.exists()) {
return fPython;
}
return null;
}
public void getSysArgv() {
sysArgv = new ArrayList<String>();
if (null == cInterpreter) {
sysArgv = null;
return;
}
try {
Object aState = mGetSystemState.invoke(interpreter, (Object[]) null);
Field fArgv = aState.getClass().getField("argv");
Object pyArgv = fArgv.get(aState);
Integer argvLen = (Integer) mLen.invoke(pyArgv, (Object[]) null);
for (int i = 0; i < argvLen; i++) {
String entry = (String) mGet.invoke(pyArgv, i);
log(lvl + 1, "sys.path[%2d] = %s", i, entry);
sysArgv.add(entry);
}
} catch (Exception ex) {
sysArgv = null;
}
}
public void setSysArgv(String[] args) {
if (null == cInterpreter || null == sysArgv) {
return;
}
try {
Object aState = mGetSystemState.invoke(interpreter, (Object[]) null);
Field fArgv = aState.getClass().getField("argv");
Object pyArgv = fArgv.get(aState);
mClear.invoke(pyArgv, null);
for (String arg : args) {
mAdd.invoke(pyArgv, arg);
}
} catch (Exception ex) {
sysArgv = null;
}
}
public void getSysPath() {
sysPath = new ArrayList<String>();
if (null == cInterpreter) {
sysPath = null;
return;
}
try {
Object aState = mGetSystemState.invoke(interpreter, (Object[]) null);
Field fPath = aState.getClass().getField("path");
Object pyPath = fPath.get(aState);
Integer pathLen = (Integer) mLen.invoke(pyPath, (Object[]) null);
for (int i = 0; i < pathLen; i++) {
String entry = (String) mGet.invoke(pyPath, i);
log(lvl + 1, "sys.path[%2d] = %s", i, entry);
sysPath.add(entry);
}
} catch (Exception ex) {
sysPath = null;
}
}
public void setSysPath() {
if (null == cInterpreter || null == sysPath) {
return;
}
try {
Object aState = mGetSystemState.invoke(interpreter, (Object[]) null);
Field fPath = aState.getClass().getField("path");
Object pyPath = fPath.get(aState);
Integer pathLen = (Integer) mLen.invoke(pyPath, (Object[]) null);
for (int i = 0; i < pathLen && i < sysPath.size(); i++) {
String entry = sysPath.get(i);
log(lvl + 1, "sys.path.set[%2d] = %s", i, entry);
mSet.invoke(pyPath, i, entry);
}
if (pathLen < sysPath.size()) {
for (int i = pathLen; i < sysPath.size(); i++) {
String entry = sysPath.get(i);
log(lvl + 1, "sys.path.add[%2d] = %s", i, entry);
mAdd.invoke(pyPath, entry);
}
}
if (pathLen > sysPath.size()) {
for (int i = sysPath.size(); i < pathLen; i++) {
String entry = (String) mGet.invoke(pyPath, i);
log(lvl + 1, "sys.path.rem[%2d] = %s", i, entry);
mRemove.invoke(pyPath, i);
}
}
} catch (Exception ex) {
sysPath = null;
}
}
public void addSitePackages() {
File fLibFolder = runTime.fSikulixLib;
File fSitePackages = new File(fLibFolder, "site-packages");
if (fSitePackages.exists()) {
addSysPath(fSitePackages);
if (hasSysPath(fSitePackages.getAbsolutePath())) {
log(lvl, "added as Jython::sys.path[0]:\n%s", fSitePackages);
}
File fSites = new File(fSitePackages, "sites.txt");
String sSites = "";
if (fSites.exists()) {
sSites = FileManager.readFileToString(fSites);
if (!sSites.isEmpty()) {
log(lvl, "found Lib/site-packages/sites.txt");
String[] listSites = sSites.split("\n");
for (String site : listSites) {
String path = site.trim();
if (!path.isEmpty()) {
appendSysPath(path);
log(lvl, "adding from Lib/site-packages/sites.txt:\n%s", path);
}
}
}
}
}
String fpBundle = ImagePath.getPath(0);
if (fpBundle != null) {
addSysPath(fpBundle);
}
}
public void addSysPath(String fpFolder) {
if (!hasSysPath(fpFolder)) {
sysPath.add(0, fpFolder);
setSysPath();
nPathAdded++;
}
}
public void appendSysPath(String fpFolder) {
if (!hasSysPath(fpFolder)) {
sysPath.add(fpFolder);
setSysPath();
nPathAdded++;
}
}
public void putSysPath(String fpFolder, int n) {
if (n < 1 || n > sysPath.size()) {
addSysPath(fpFolder);
} else {
sysPath.add(n, fpFolder);
setSysPath();
nPathAdded++;
}
}
public void addSysPath(File fFolder) {
addSysPath(fFolder.getAbsolutePath());
}
public void insertSysPath(File fFolder) {
getSysPath();
sysPath.add((nPathSaved > -1 ? nPathSaved : 0), fFolder.getAbsolutePath());
setSysPath();
nPathSaved = -1;
}
public void removeSysPath(File fFolder) {
int n;
if (-1 < (n = getSysPathEntry(fFolder))) {
sysPath.remove(n);
nPathSaved = n;
setSysPath();
nPathAdded = nPathAdded == 0 ? 0 : nPathAdded--;
}
}
public boolean hasSysPath(String fpFolder) {
getSysPath();
for (String fpPath : sysPath) {
if (FileManager.pathEquals(fpPath, fpFolder)) {
return true;
}
}
return false;
}
public int getSysPathEntry(File fFolder) {
getSysPath();
int n = 0;
for (String fpPath : sysPath) {
if (FileManager.pathEquals(fpPath, fFolder.getAbsolutePath())) {
return n;
}
n++;
}
return -1;
}
public File existsSysPathModule(String modname) {
getSysPath();
File fModule = null;
for (String fpPath : sysPath) {
fModule = existsModule(modname, new File(fpPath));
if (null != fModule) {
break;
}
}
return fModule;
}
public File existsSysPathJar(String fpJar) {
getSysPath();
File fJar = null;
for (String fpPath : sysPath) {
fJar = new File(fpPath, fpJar);
if (fJar.exists()) {
break;
}
fJar = null;
}
return fJar;
}
public void showSysPath() {
if (Debug.is(lvl)) {
getSysPath();
log(lvl, "***** Jython sys.path");
for (int i = 0; i < sysPath.size(); i++) {
logp(lvl, "%2d: %s", i, sysPath.get(i));
}
log(lvl, "***** Jython sys.path end");
}
}
public String getCurrentLine() {
String trace = "";
Object frame = null;
Object back = null;
try {
Method mGetFrame = cPy.getMethod("getFrame", nc);
Class cPyFrame = Class.forName("org.python.core.PyFrame");
Field fLineno = cPyFrame.getField("f_lineno");
Field fCode = cPyFrame.getField("f_code");
Field fBack = cPyFrame.getField("f_back");
Class cPyBaseCode = Class.forName("org.python.core.PyBaseCode");
Field fFilename = cPyBaseCode.getField("co_filename");
frame = mGetFrame.invoke(cPy, (Object[]) null);
back = fBack.get(frame);
if (null == back) {
trace = "Jython: at " + getCurrentLineTraceElement(fLineno, fCode, fFilename, frame);
} else {
trace = "Jython traceback - current first:\n"
+ getCurrentLineTraceElement(fLineno, fCode, fFilename, frame);
while (null != back) {
String line = getCurrentLineTraceElement(fLineno, fCode, fFilename, back);
if (! line.startsWith("Region (")) {
trace += "\n" + line;
}
back = fBack.get(back);
}
}
} catch (Exception ex) {
trace = String.format("Jython: getCurrentLine: INSPECT EXCEPTION: %s", ex);
}
return trace;
}
private String getCurrentLineTraceElement(Field fLineno, Field fCode, Field fFilename, Object frame) {
String trace = "";
try {
int lineno = fLineno.getInt(frame);
Object code = fCode.get(frame);
Object filename = fFilename.get(code);
String fname = FileManager.getName((String) filename);
fname = fname.replace(".py", "");
trace = String.format("%s (%d)", fname, lineno);
} catch (Exception ex) {
}
return trace;
}
}