blob: a68592b1b57970a431a0ec04a2333efb4c183a3d [file] [log] [blame]
#################################################################################
# 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 v1.0
# which accompanies this distribution, and is available at
# http://www.eclipse.org/legal/epl-v10.html
#
# Contributors:
# Jonah Graham (Kichwa Coders) - initial API and implementation
#################################################################################
import code
import os
import py4j
from py4j.clientserver import ClientServer, JavaParameters, PythonParameters
from py4j.java_collections import MapConverter
from py4j.java_gateway import JavaObject
from py4j.protocol import Py4JJavaError, get_command_part
import sys
import threading
# 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())
class EaseInteractiveConsole(code.InteractiveConsole):
'''
Extension to standard InteractiveConsole so we can handle and capture
error output better
'''
def __init__(self, engine, *args, **kwargs):
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):
try:
exec(code, self.locals)
except SystemExit:
raise
except Py4JJavaError as e:
if isinstance(e.java_exception, 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:
# create information that will end up in a
# ScriptExecutionException
self.showtraceback()
class 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):
self.gateway_client = gateway_client
self.display_data = display_data
self.except_data = except_data
def getException(self):
data = self.except_data
if data is None:
return None
if isinstance(data, JavaObject):
return data;
return "".join(data)
def getResult(self):
data = self.display_data
try:
# test if py4j understands this type
get_command_part(data, dict())
# py4j knows how to convert this to Java,
# just return it as is
return data
except:
pass
# try registered converters
for converter in self.gateway_client.converters:
if converter.can_convert(data):
return converter.convert(data, self.gateway_client)
# data cannot be represented in Java, return a string representation
# instead
return repr(data)
class Java:
implements = ['org.eclipse.ease.lang.python.py4j.internal.IInteractiveReturn']
class ScriptEngineExecute(object):
'''
Instance of Java's IPythonSideEngine.
This class is the main class of the Python side.
'''
def __init__(self):
self.shutdown_event = threading.Event()
def set_gateway(self, gateway):
self.gateway = gateway
self.locals = dict()
self.interp = 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
self.locals['javax'] = gateway.jvm.javax
self.locals['org'] = gateway.jvm.org
self.locals['com'] = gateway.jvm.com
self.locals['net'] = gateway.jvm.net
self.locals['jvm'] = gateway.jvm
self.locals['gateway'] = gateway
self.locals['py4j'] = py4j
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):
self.display_data = None
self.except_data = None
needMore = code_exec(code_text)
if needMore:
# TODO, need to handle this with prompts, this message
# is a workaround
return 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 InteractiveReturn(self.gateway._gateway_client, display_data=display_data, except_data=except_data)
def executeScript(self, code_text, filename=None):
# TODO: Handle filename
return self.executeCommon(code_text, self.interp.runcode)
def executeInteractive(self, code_text):
return self.executeCommon(code_text, self.interp.push)
def internalGetVariable(self, name):
return repr(self.locals.get(name))
def internalGetVariables(self):
locals_repr = dict()
for k, v in self.locals.items():
if not k.startswith("__"):
locals_repr[k] = repr(v)
converted = MapConverter().convert(locals_repr, self.gateway._gateway_client)
return converted
def internalHasVariable(self, name):
return name in self.locals
def internalSetVariable(self, name, content):
self.locals[name] = content
def teardownEngine(self):
self.shutdown_event.set()
def wait_on_shutdown(self):
self.shutdown_event.wait()
class Java:
implements = ['org.eclipse.ease.lang.python.py4j.internal.IPythonSideEngine']
def watchdog(engine):
# Read from the parent process until EOF, EOF indicates the
# parent process has terminated
try:
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 = threading.Timer(10.0, os._exit, (1,))
timer.setDaemon(True)
timer.start()
def main(argv):
port = int(argv[1])
engine = ScriptEngineExecute()
gateway = ClientServer(java_parameters=JavaParameters(auto_convert=True, port=port),
python_parameters=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 = threading.Thread(target=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__':
main(sys.argv)