| ############################################################################### |
| # Copyright (c) 2016 Kichwa Coders and others. |
| # All rights reserved. This program and the accompanying materials |
| # are made available under the terms of the Eclipse Public License v2.0 |
| # which accompanies this distribution, and is available at |
| # https://www.eclipse.org/legal/epl-2.0/ |
| # |
| # Contributors: |
| # Jonah Graham (Kichwa Coders) - initial API and implementation |
| ############################################################################### |
| import sys as _pyease_sys |
| if __name__ == '__main__': |
| # To be able to import all the py4j and related items, we need to add to the PYTHONPATH |
| # all the correct paths. Because we may be launched with -E, the command line provides |
| # all the paths to what we need. |
| # sys.argv[1] - required - the port to conenct o |
| # sys.argv[2:] - optional - paths to prepend on sys.path |
| _pyease_sys.path[0:0] = _pyease_sys.argv[2:] |
| |
| import code as _pyease_code |
| import os as _pyease_os |
| import py4j |
| from py4j.clientserver import ClientServer as _pyease_ClientServer |
| from py4j.clientserver import JavaParameters as _pyease_JavaParameters |
| from py4j.clientserver import PythonParameters as _pyease_PythonParameters |
| from py4j.java_collections import MapConverter as _pyease_MapConverter |
| from py4j.java_collections import ListConverter as _pyease_ListConverter |
| from py4j.java_collections import SetConverter as _pyease_SetConverter |
| from py4j.java_gateway import JavaObject as _pyease_JavaObject |
| from py4j.java_gateway import JavaClass as _pyease_JavaClass |
| from py4j.protocol import Py4JJavaError as _pyease_Py4JJavaError |
| import threading as _pyease_threading |
| import __main__ |
| import ast as _pyease_ast |
| try: |
| from six import integer_types as _pyease_integer_types |
| from six import string_types as _pyease_string_types |
| except ImportError: |
| if _pyease_sys.version_info.major == 2: |
| _pyease_integer_types = (int, long) |
| _pyease_string_types = (basestring,) |
| else: |
| _pyease_integer_types = (int,) |
| _pyease_string_types = (str,) |
| |
| # builtins used to set global variables |
| try: |
| # Python 3.* |
| import builtins as _pyease_builtins |
| except ImportError: |
| # Python 2.* |
| _pyease_builtins = __builtins__ |
| |
| |
| def _pyease_patch_builtins(name, value): |
| ''' |
| Patches Python builtins to make given value available as a builtin |
| Python object in all modules. |
| |
| :param name: Name of the builtin |
| :type name: str |
| :param value: Value for the builtin |
| :type value: object |
| ''' |
| _pyease_builtins.__dict__.update({name: value}) |
| |
| |
| # To ease some debugging of the py4j engine itself it is useful to turn logging on, |
| # uncomment the following lines for one way to do that |
| # import logging |
| # logger = logging.getLogger("py4j") |
| # logger.setLevel(logging.DEBUG) |
| # logger.addHandler(logging.StreamHandler()) |
| def convert_value( |
| value, |
| gw=None, |
| integer_types=_pyease_integer_types, |
| string_types=_pyease_string_types, |
| dict_types=dict, |
| list_types=list, |
| set_types=set): |
| ''' |
| Tries to convert the given value using different strategies. |
| |
| Used because default py4j converters have some issues with e.g. |
| nested collections. |
| |
| :param value: Value to be converted. |
| :param gw: py4j gateway necessary for converters. |
| ''' |
| if not gw: |
| gw = gateway._gateway_client |
| |
| # None will be mapped to Java null |
| if value is None: |
| return value |
| |
| # Integers can be send directly |
| if isinstance(value, integer_types): |
| if (value > java.lang.Long.MAX_VALUE) or (value < java.lang.Long.MIN_VALUE): |
| return java.math.BigInteger(str(value)) |
| |
| return value |
| |
| # Floats can be send directly |
| if isinstance(value, float): |
| return value |
| |
| # Strings can be send directly |
| if isinstance(value, string_types): |
| return value |
| |
| # Recursively check collections |
| if isinstance(value, dict_types): |
| return _pyease_MapConverter().convert({ |
| convert_value(k, gw): convert_value(v, gw) |
| for k, v in value.items() |
| }, gw) |
| if isinstance(value, list_types): |
| return _pyease_ListConverter().convert([ |
| convert_value(v, gw) for v in value |
| ], gw) |
| if isinstance(value, set_types): |
| return _pyease_SetConverter().convert({ |
| convert_value(v, gw) for v in value |
| }, gw) |
| |
| # Try registered converters |
| for converter in gw.converters: |
| if converter.can_convert(value): |
| try: |
| return converter.convert(value, gw) |
| except Exception: |
| # TODO: Actually find out what this might throw |
| pass |
| |
| # Issues with marshalling Java class objects |
| if isinstance(value, _pyease_JavaClass): |
| return repr(value) |
| |
| # Check if we have a Java object |
| if isinstance(value, _pyease_JavaObject): |
| return value |
| |
| # Check if we implement a Java interface |
| if hasattr(value, 'Java') and hasattr(getattr(value, 'Java'), 'implements'): |
| if hasattr(value, '_get_obj_id'): |
| if callable(value._get_obj_id): |
| return value |
| else: |
| # Issue with invalid implementations |
| if hasattr(value, 'toString'): |
| return value |
| |
| # Last resort use string representation |
| return repr(value) |
| |
| convert_value.__ease__ = True |
| |
| class _pyease_EaseInteractiveConsole(_pyease_code.InteractiveConsole): |
| ''' |
| Extension to standard InteractiveConsole so we can handle and capture |
| error output better |
| ''' |
| |
| def __init__(self, engine, *args, **kwargs): |
| _pyease_code.InteractiveConsole.__init__(self, *args, **kwargs) |
| self.engine = engine |
| |
| def write(self, data): |
| # Python 3 write the whole error in one write, Python 2 does multiple writes |
| if self.engine.except_data is None: |
| self.engine.except_data = [data] |
| else: |
| self.engine.except_data.append(data) |
| |
| def runcode(self, code, filename=None): |
| filename = filename or '<...>' |
| try: |
| # If we have compiled code we cannot use AST |
| if isinstance(code, _pyease_string_types): |
| # Parse code input |
| tree = _pyease_ast.parse(code) |
| |
| # Check if we have multiline statement |
| if len(tree.body) > 1: |
| module = _pyease_ast.Module(tree.body[:-1], type_ignores=[]) |
| compiled = compile(module, filename, 'exec') |
| exec(compiled, self.locals) |
| |
| # Check if at least one line given |
| if len(tree.body): |
| if isinstance(tree.body[-1], _pyease_ast.Expr): |
| # Only expressions can be evaluated |
| expression = _pyease_ast.Expression(tree.body[-1].value) |
| compiled = compile(expression, filename, 'eval') |
| result = eval(compiled, self.locals) |
| return result, False |
| else: |
| module = _pyease_ast.Module([tree.body[-1]], type_ignores=[]) |
| compiled = compile(module, filename, 'exec') |
| exec(compiled, self.locals) |
| else: |
| exec(code, self.locals) |
| |
| except SystemExit: |
| raise |
| except _pyease_Py4JJavaError as e: |
| if isinstance(e.java_exception, _pyease_JavaObject): |
| # Skip self.showtraceback/self.write as we can get |
| # a Java exception instance already |
| self.engine.except_data = e.java_exception |
| else: |
| # No java exception here, fallback to normal case |
| self.showtraceback() |
| except Exception: |
| # create information that will end up in a |
| # ScriptExecutionException |
| self.showtraceback() |
| |
| def push(self, cmd, filename=None): |
| return _pyease_code.InteractiveConsole.push(self, cmd) |
| |
| |
| # Sentinel object for no result (different than None) |
| _pyease_NO_RESULT = object() |
| |
| |
| class _pyease_InteractiveReturn(object): |
| ''' |
| Instance of Java's IInteractiveReturn. |
| This class encapsulates the return state from the |
| ScriptEngineExecute.executeInteractive() method |
| ''' |
| |
| def __init__(self, gateway_client, display_data=None, except_data=None, result=_pyease_NO_RESULT): |
| self.gateway_client = gateway_client |
| self.display_data = display_data |
| self.except_data = except_data |
| self.result = result |
| |
| def getException(self): |
| data = self.except_data |
| if data is None: |
| return None |
| if isinstance(data, _pyease_JavaObject): |
| return data |
| return "".join(data) |
| |
| def getResult(self): |
| # Check if we did not receive result |
| if self.result is _pyease_NO_RESULT: |
| data = self.display_data |
| else: |
| data = self.result |
| |
| # Use conversion just to be sure |
| return convert_value(data, self.gateway_client) |
| |
| class Java: |
| implements = ['org.eclipse.ease.lang.python.py4j.internal.IInteractiveReturn'] |
| |
| |
| class _pyease_ScriptEngineExecute(object): |
| ''' |
| Instance of Java's IPythonSideEngine. |
| This class is the main class of the Python side. |
| ''' |
| |
| def __init__(self): |
| self.shutdown_event = _pyease_threading.Event() |
| |
| def set_gateway(self, gateway): |
| self.gateway = gateway |
| self.locals = __main__.__dict__ |
| self.interp = _pyease_EaseInteractiveConsole(self, self.locals) |
| # Provide most common top level pacakage names in the namespace |
| # so that code like "java.lang.String()" can work. |
| # for other names, jvm.<package name> should be used |
| self.locals['java'] = gateway.jvm.java |
| _pyease_patch_builtins('java', gateway.jvm.java) |
| |
| self.locals['javax'] = gateway.jvm.javax |
| _pyease_patch_builtins('javax', gateway.jvm.javax) |
| |
| self.locals['org'] = gateway.jvm.org |
| _pyease_patch_builtins('org', gateway.jvm.org) |
| |
| self.locals['com'] = gateway.jvm.com |
| _pyease_patch_builtins('com', gateway.jvm.com) |
| |
| self.locals['net'] = gateway.jvm.net |
| _pyease_patch_builtins('net', gateway.jvm.net) |
| |
| self.locals['jvm'] = gateway.jvm |
| _pyease_patch_builtins('jvm', gateway.jvm.jvm) |
| |
| self.locals['gateway'] = gateway |
| _pyease_patch_builtins('gateway', gateway) |
| |
| self.locals['py4j'] = py4j |
| _pyease_patch_builtins('py4j', py4j) |
| |
| _pyease_sys.displayhook = self.displayhook |
| self.display_data = None |
| self.except_data = None |
| |
| def displayhook(self, data): |
| self.display_data = data |
| if data is not None: |
| self.locals['_'] = data |
| |
| def executeCommon(self, code_text, code_exec, filename=None): |
| self.display_data = None |
| self.except_data = None |
| execution_result = code_exec(code_text, filename) |
| |
| # Check if we received a tuple, meaning that we are in script mode |
| if isinstance(execution_result, tuple) and len(execution_result) == 2: |
| result, needMore = execution_result |
| else: |
| # Set result to NO_RESULT to distinguish from None result |
| result = _pyease_NO_RESULT |
| needMore = execution_result |
| |
| if needMore: |
| # TODO, need to handle this with prompts, this message |
| # is a workaround |
| return _pyease_InteractiveReturn(self.gateway._gateway_client, display_data="... - more input required to complete statement") |
| else: |
| display_data = self.display_data |
| except_data = self.except_data |
| self.display_data = None |
| self.except_data = None |
| return _pyease_InteractiveReturn(self.gateway._gateway_client, display_data=display_data, except_data=except_data, result=result) |
| |
| def executeScript(self, code_text, filename=None): |
| return self.executeCommon(code_text, self.interp.runcode, filename) |
| |
| def executeInteractive(self, code_text): |
| return self.executeCommon(code_text, self.interp.push) |
| |
| def internalGetVariable(self, name): |
| return convert_value(self.locals.get(name), self.gateway._gateway_client) |
| |
| def internalGetVariables(self): |
| # Filter out data we do not want |
| filtered = { |
| k: convert_value(v, self.gateway._gateway_client) |
| for k, v in self.locals.items() |
| if k.startswith('__EASE') or ((not k.startswith('__')) and (not k.startswith('_pyease_')) and (not v.__class__.__module__.startswith('py4j.')) and (not hasattr(v, '__ease__')) and (not v.__class__.__name__ == 'module')) |
| } |
| |
| return _pyease_MapConverter().convert(filtered, self.gateway._gateway_client) |
| |
| def internalHasVariable(self, name): |
| return name in self.locals |
| |
| def internalSetVariable(self, name, content): |
| self.locals[name] = content |
| _pyease_builtins.__dict__.update({name: content}) |
| |
| def teardownEngine(self): |
| self.shutdown_event.set() |
| |
| def wait_on_shutdown(self): |
| self.shutdown_event.wait() |
| |
| def addSearchPath(self, path): |
| _pyease_sys.path.append(path) |
| |
| class Java: |
| implements = ['org.eclipse.ease.lang.python.py4j.internal.IPythonSideEngine'] |
| |
| |
| def _pyease_watchdog(engine): |
| # Read from the parent process until EOF, EOF indicates the |
| # parent process has terminated |
| try: |
| _pyease_sys.stdin.read() |
| except: |
| pass |
| |
| # shutdown the engine |
| engine.teardownEngine() |
| |
| # Allow some time for the shutdown to be clean, but |
| # fallback to a hard exit if that fails |
| timer = _pyease_threading.Timer(10.0, _pyease_os._exit, (1,)) |
| timer.setDaemon(True) |
| timer.start() |
| |
| |
| def _pyease_main(argv): |
| port = int(argv[1]) |
| engine = _pyease_ScriptEngineExecute() |
| # Bug 517528: Disable memory management until Py4J #275 is resolved |
| enable_memory_management = False |
| java_params = _pyease_JavaParameters(auto_convert=True, port=port, |
| enable_memory_management=enable_memory_management) |
| gateway = _pyease_ClientServer(java_parameters=java_params, |
| python_parameters=_pyease_PythonParameters(port=0), |
| python_server_entry_point=engine) |
| # retrieve the port on which the python callback server was bound to. |
| python_port = gateway.get_callback_server().get_listening_port() |
| engine.set_gateway(gateway) |
| |
| # tell Java that we are up and running and where to direct |
| # calls to python to. |
| gateway.entry_point.pythonStartupComplete(python_port, engine) |
| |
| # start a watchdog on stdin to make sure we terminate |
| thread = _pyease_threading.Thread(target=_pyease_watchdog, args=(engine,)) |
| thread.setDaemon(True) |
| thread.start() |
| |
| # now wait until we have a request for shutdown |
| engine.wait_on_shutdown() |
| |
| gateway.shutdown_callback_server() |
| gateway.shutdown() |
| |
| |
| if __name__ == '__main__': |
| # Will be patched via builtins |
| gateway = None |
| _pyease_main(_pyease_sys.argv) |