/**
* Copyright (c) 2005-2011 by Appcelerator, Inc. All Rights Reserved.
* Licensed under the terms of the Eclipse Public License (EPL).
* Please see the license.txt included with this distribution for details.
* Any modifications to this file must keep this entire header intact.
*/
/*
* Created on 13/08/2005
*/
package org.python.pydev.editor.codecompletion.shell;
import java.io.File;
import java.io.IOException;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketTimeoutException;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.StringTokenizer;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.python.copiedfromeclipsesrc.JDTNotAvailableException;
import org.python.pydev.core.IInterpreterInfo;
import org.python.pydev.core.IInterpreterManager;
import org.python.pydev.core.IPythonNature;
import org.python.pydev.core.IToken;
import org.python.pydev.core.MisconfigurationException;
import org.python.pydev.core.PythonNatureWithoutProjectException;
import org.python.pydev.core.log.Log;
import org.python.pydev.editor.codecompletion.PyCodeCompletionPreferencesPage;
import org.python.pydev.editor.codecompletion.revisited.ModulesManager;
import org.python.pydev.logging.DebugSettings;
import org.python.pydev.plugin.PydevPlugin;
import com.aptana.shared_core.net.LocalHost;
import com.aptana.shared_core.net.SocketUtil;
import com.aptana.shared_core.structure.Tuple;
/**
* This is the shell that 'talks' to the python / jython process (it is intended to be subclassed so that
* we know how to deal with each).
*
* Its methods are synched to prevent concurrent access.
*
* @author fabioz
*
*/
public abstract class AbstractShell {
public static final int BUFFER_SIZE = 1024;
public static final int OTHERS_SHELL = 2;
public static final int COMPLETION_SHELL = 1;
protected static final int DEFAULT_SLEEP_BETWEEN_ATTEMPTS = 1000; //1sec, so we can make the number of attempts be shown as elapsed in secs
protected static final int DEBUG_SHELL = -1;
private final String TYPE_UNKNOWN_STR = "" + IToken.TYPE_UNKNOWN;
/**
* Determines if we are already in a method that starts the shell
*/
private boolean inStart = false;
/**
* Determines if we are (theoretically) already connected (meaning that trying to start the shell
* again will not do anything)
*
* Ending the shell sets this to false and starting it sets it to true (if successful)
*/
private boolean isConnected = false;
private boolean isInRead = false;
private boolean isInWrite = false;
private boolean isInRestart = false;
private IInterpreterInfo shellInterpreter;
private int shellMillis;
/**
* Lock to know if there is someone already using this shell for some operation
*/
private boolean isInOperation = false;
private static void dbg(String string, int priority) {
if (priority <= DEBUG_SHELL) {
System.out.println(string);
}
if (DebugSettings.DEBUG_CODE_COMPLETION) {
Log.toLogFile(string, AbstractShell.class);
}
}
/**
* the encoding used to encode messages
*/
private static final String ENCODING_UTF_8 = "UTF-8";
/**
* Reference to 'global python shells'
*
* this works as follows:
* we have the interpreter as that the shell is related to as the 1st key
*
* and then we have the id with the shell type that points to the actual shell
*
* @see #COMPLETION_SHELL
* @see #OTHERS_SHELL
*/
protected static Map<String, Map<Integer, AbstractShell>> shells = new HashMap<String, Map<Integer, AbstractShell>>();
/**
* if we are already finished for good, we may not start new shells (this is a static, because this
* should be set only at shutdown).
*/
private static boolean finishedForGood = false;
/**
* simple stop of a shell (it may be later restarted)
*/
public synchronized static void stopServerShell(IInterpreterInfo interpreter, int id) {
synchronized (shells) {
Map<Integer, AbstractShell> typeToShell = getTypeToShellFromId(interpreter);
AbstractShell pythonShell = (AbstractShell) typeToShell.get(new Integer(id));
if (pythonShell != null) {
try {
pythonShell.endIt();
} catch (Exception e) {
// ignore... we are ending it anyway...
}
}
typeToShell.remove(id); //there's no exception if it was not there in the 1st place...
}
}
/**
* stops all registered shells
*
*/
public synchronized static void shutdownAllShells() {
synchronized (shells) {
if (DebugSettings.DEBUG_CODE_COMPLETION) {
Log.toLogFile("Shutting down all shells (for good)...", AbstractShell.class);
}
for (Iterator<Map<Integer, AbstractShell>> iter = shells.values().iterator(); iter.hasNext();) {
finishedForGood = true; //we may no longer restart shells
Map<Integer, AbstractShell> rel = (Map<Integer, AbstractShell>) iter.next();
if (rel != null) {
for (Iterator<AbstractShell> iter2 = rel.values().iterator(); iter2.hasNext();) {
AbstractShell element = iter2.next();
if (element != null) {
try {
element.shutdown(); //shutdown
} catch (Exception e) {
Log.log(e); //let's log it... this should not happen
}
}
}
}
}
shells.clear();
}
}
/**
* Restarts all the shells and clears any related cache.
*
* @return an error message if some exception happens in this process (an empty string means all went smoothly).
*/
public static String restartAllShells() {
String ret = "";
synchronized (shells) {
try {
if (DebugSettings.DEBUG_CODE_COMPLETION) {
Log.toLogFile("Restarting all shells and clearing caches...", AbstractShell.class);
}
for (Map<Integer, AbstractShell> val : shells.values()) {
for (AbstractShell val2 : val.values()) {
if (val2 != null) {
val2.endIt();
}
}
IInterpreterManager[] interpreterManagers = PydevPlugin.getAllInterpreterManagers();
for (IInterpreterManager iInterpreterManager : interpreterManagers) {
if (iInterpreterManager == null) {
continue; //Should happen only on testing...
}
try {
iInterpreterManager.clearCaches();
} catch (Exception e) {
Log.log(e);
ret += e.getMessage() + "\n";
}
}
//Clear the global modules cache!
ModulesManager.clearCache();
}
} catch (Exception e) {
Log.log(e);
ret += e.getMessage() + "\n";
}
}
return ret;
}
/**
* @param interpreter the interpreter whose shell we want.
* @return a map with the type of the shell mapping to the shell itself
*/
private synchronized static Map<Integer, AbstractShell> getTypeToShellFromId(IInterpreterInfo interpreter) {
synchronized (shells) {
Map<Integer, AbstractShell> typeToShell = shells.get(interpreter.getExecutableOrJar());
if (typeToShell == null) {
typeToShell = new HashMap<Integer, AbstractShell>();
shells.put(interpreter.getExecutableOrJar(), typeToShell);
}
return typeToShell;
}
}
/**
* register a shell and give it an id
*
* @param nature the nature (which has the information on the interpreter we want to used)
* @param id the shell id
* @see #COMPLETION_SHELL
* @see #OTHERS_SHELL
*
* @param shell the shell to register
*/
public synchronized static void putServerShell(IPythonNature nature, int id, AbstractShell shell) {
synchronized (shells) {
try {
Map<Integer, AbstractShell> typeToShell = getTypeToShellFromId(nature.getProjectInterpreter());
typeToShell.put(new Integer(id), shell);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
public synchronized static AbstractShell getServerShell(IPythonNature nature, int id) throws IOException,
JDTNotAvailableException, CoreException, MisconfigurationException, PythonNatureWithoutProjectException {
return getServerShell(nature.getProjectInterpreter(), nature.getInterpreterType(), id);
}
/**
* @param interpreter the interpreter that should create the shell
*
* @param relatedTo identifies to which kind of interpreter the shell should be related.
* @see org.python.pydev.core.IPythonNature#INTERPRETER_TYPE_PYTHON
* @see org.python.pydev.core.IPythonNature#INTERPRETER_TYPE_JYTHON
*
* @param a given id for the shell
* @see #COMPLETION_SHELL
* @see #OTHERS_SHELL
*
* @return the shell with the given id related to some nature
*
* @throws CoreException
* @throws IOException
* @throws MisconfigurationException
*/
private synchronized static AbstractShell getServerShell(IInterpreterInfo interpreter, int relatedTo, int id)
throws IOException, JDTNotAvailableException, CoreException, MisconfigurationException {
AbstractShell pythonShell = null;
synchronized (shells) {
if (DebugSettings.DEBUG_CODE_COMPLETION) {
Log.toLogFile("Synchronizing on shells...", AbstractShell.class);
}
if (DebugSettings.DEBUG_CODE_COMPLETION) {
String flavor;
switch (relatedTo) {
case IPythonNature.INTERPRETER_TYPE_JYTHON:
flavor = "Jython";
break;
case IPythonNature.INTERPRETER_TYPE_IRONPYTHON:
flavor = "IronPython";
break;
default:
flavor = "Python";
}
;
Log.toLogFile(
"Getting shell related to:" + flavor + " id:" + id + " interpreter: "
+ interpreter.getExecutableOrJar(), AbstractShell.class);
}
Map<Integer, AbstractShell> typeToShell = getTypeToShellFromId(interpreter);
pythonShell = (AbstractShell) typeToShell.get(new Integer(id));
if (pythonShell == null) {
if (DebugSettings.DEBUG_CODE_COMPLETION) {
Log.toLogFile("pythonShell == null", AbstractShell.class);
}
if (relatedTo == IPythonNature.INTERPRETER_TYPE_PYTHON) {
pythonShell = new PythonShell();
} else if (relatedTo == IPythonNature.INTERPRETER_TYPE_JYTHON) {
pythonShell = new JythonShell();
} else if (relatedTo == IPythonNature.INTERPRETER_TYPE_IRONPYTHON) {
pythonShell = new IronpythonShell();
} else {
throw new RuntimeException("unknown related id");
}
if (DebugSettings.DEBUG_CODE_COMPLETION) {
Log.toLogFile("pythonShell.startIt()", AbstractShell.class);
Log.addLogLevel();
}
pythonShell.startIt(interpreter, AbstractShell.DEFAULT_SLEEP_BETWEEN_ATTEMPTS); //first start it
if (DebugSettings.DEBUG_CODE_COMPLETION) {
Log.remLogLevel();
Log.toLogFile("Finished pythonShell.startIt()", AbstractShell.class);
}
//then make it accessible
typeToShell.put(new Integer(id), pythonShell);
}
}
return pythonShell;
}
/**
* Python server process.
*/
protected Process process;
/**
* We should write in this socket.
*/
protected Socket socketToWrite;
/**
* We should read this socket.
*/
protected Socket socketToRead;
/**
* Python file that works as the server.
*/
protected File serverFile;
/**
* Server socket (accept connections).
*/
protected ServerSocket serverSocket;
/**
* Initialize given the file that points to the python server (execute it
* with python).
*
* @param f file pointing to the python server
*
* @throws IOException
* @throws CoreException
*/
protected AbstractShell(File f) throws IOException, CoreException {
if (finishedForGood) {
throw new RuntimeException(
"Shells are already finished for good, so, it is an invalid state to try to create a new shell.");
}
serverFile = f;
if (!serverFile.exists()) {
throw new RuntimeException("Can't find python server file");
}
}
/**
* Just wait a little...
*/
protected synchronized void sleepALittle(int t) {
try {
synchronized (this) {
wait(t); //millis
}
} catch (InterruptedException e) {
}
}
/**
* This method creates the python server process and starts the sockets, so that we
* can talk with the server.
* @throws IOException
* @throws CoreException
* @throws MisconfigurationException
* @throws PythonNatureWithoutProjectException
*/
/*package*/synchronized void startIt(IPythonNature nature) throws IOException, JDTNotAvailableException,
CoreException, MisconfigurationException, PythonNatureWithoutProjectException {
synchronized (this) {
this.startIt(nature.getProjectInterpreter(), AbstractShell.DEFAULT_SLEEP_BETWEEN_ATTEMPTS);
}
}
/**
* This method creates the python server process and starts the sockets, so that we
* can talk with the server.
*
* @param milisSleep: time to wait after creating the process.
* @throws IOException is some error happens creating the sockets - the process is terminated.
* @throws JDTNotAvailableException
* @throws CoreException
* @throws CoreException
* @throws MisconfigurationException
*/
protected synchronized void startIt(IInterpreterInfo interpreter, int milisSleep) throws IOException,
JDTNotAvailableException, CoreException, MisconfigurationException {
this.shellMillis = milisSleep;
this.shellInterpreter = interpreter;
if (inStart || isConnected) {
//it is already in the process of starting, so, if we are in another thread, just forget about it.
return;
}
inStart = true;
try {
if (finishedForGood) {
throw new RuntimeException(
"Shells are already finished for good, so, it is an invalid state to try to restart it.");
}
try {
serverSocket = new ServerSocket(0); //read in this port
int pRead = serverSocket.getLocalPort();
SocketUtil.checkValidPort(pRead);
int pWrite = SocketUtil.findUnusedLocalPorts(1)[0];
if (process != null) {
endIt(); //end the current process
}
ProcessCreationInfo processInfo = createServerProcess(interpreter, pWrite, pRead);
dbg("executed: " + processInfo.getProcessLog(), 1);
sleepALittle(200); //Give it some time to warmup.
if (process == null) {
String msg = "Error creating python process - got null process.\n" + processInfo.getProcessLog();
dbg(msg, 1);
Log.log(msg);
throw new CoreException(PydevPlugin.makeStatus(IStatus.ERROR, msg, new Exception(msg)));
}
try {
int exitVal = process.exitValue(); //should throw exception saying that it still is not terminated...
String msg = "Error creating python process - exited before creating sockets - exitValue = ("
+ exitVal + ").\n" + processInfo.getProcessLog();
dbg(msg, 1);
Log.log(msg);
throw new CoreException(PydevPlugin.makeStatus(IStatus.ERROR, msg, new Exception(msg)));
} catch (IllegalThreadStateException e2) { //this is ok
}
dbg("afterCreateProcess ", 1);
//ok, process validated, so, let's get its output and store it for further use.
boolean connected = false;
int attempts = 0;
dbg("connecting... ", 1);
sleepALittle(milisSleep);
socketToWrite = null;
int maxAttempts = PyCodeCompletionPreferencesPage.getNumberOfConnectionAttempts();
dbg("attempts: " + attempts, 1);
dbg("maxAttempts: " + maxAttempts, 1);
dbg("finishedForGood: " + finishedForGood, 1);
while (!connected && attempts < maxAttempts && !finishedForGood) {
attempts += 1;
dbg("connecting attept..." + attempts, 1);
try {
if (socketToWrite == null || socketToWrite.isConnected() == false) {
socketToWrite = new Socket(LocalHost.getLocalHost(), pWrite); //we should write in this port
}
if (socketToWrite != null || socketToWrite.isConnected()) {
try {
dbg("serverSocket.accept()! ", 1);
socketToRead = serverSocket.accept();
dbg("socketToRead.setSoTimeout(5000) ", 1);
socketToRead.setSoTimeout(5000); //let's give it a higher timeout, as we're already half - connected
connected = true;
dbg("connected! ", 1);
} catch (SocketTimeoutException e) {
//that's ok, timeout for waiting connection expired, let's check it again in the next loop
}
}
} catch (IOException e1) {
if (socketToWrite != null && socketToWrite.isConnected() == true) {
String msg = "Attempt: " + attempts + " of " + maxAttempts
+ " failed, trying again...(socketToWrite already binded)";
dbg(msg, 1);
Log.log(IStatus.ERROR, msg, e1);
}
if (socketToWrite != null && !socketToWrite.isConnected() == true) {
String msg = "Attempt: " + attempts + " of " + maxAttempts
+ " failed, trying again...(socketToWrite still not binded)";
dbg(msg, 1);
Log.log(IStatus.ERROR, msg, e1);
}
}
//if not connected, let's sleep a little for another attempt
if (!connected) {
sleepALittle(milisSleep);
}
}
if (!connected && !finishedForGood) {
dbg("NOT connected ", 1);
//what, after all this trouble we are still not connected????!?!?!?!
//let's communicate this to the user...
String isAlive;
try {
int exitVal = process.exitValue(); //should throw exception saying that it still is not terminated...
isAlive = " - the process in NOT ALIVE anymore (output=" + exitVal + ") - ";
} catch (IllegalThreadStateException e2) { //this is ok
isAlive = " - the process in still alive (killing it now)- ";
process.destroy();
}
String msg = "Error connecting to python process.\n" + isAlive + "\n" + processInfo.getProcessLog();
RuntimeException exception = new RuntimeException(msg);
dbg(msg, 1);
Log.log(exception);
throw exception;
}
} catch (IOException e) {
if (process != null) {
process.destroy();
process = null;
}
throw e;
}
} finally {
this.inStart = false;
}
//if it got here, everything went ok (otherwise we would have gotten an exception).
isConnected = true;
}
/**
* @param pWrite the port where we should write
* @param pRead the port where we should read
* @return a tuple with:
* - command line used to execute process
* - environment used to execute process
*
* @throws IOException
* @throws JDTNotAvailableException
* @throws MisconfigurationException
*/
protected abstract ProcessCreationInfo createServerProcess(IInterpreterInfo interpreter, int pWrite, int pRead)
throws IOException, JDTNotAvailableException, MisconfigurationException;
protected synchronized void communicateWork(String desc, IProgressMonitor monitor) {
if (monitor != null) {
monitor.setTaskName(desc);
monitor.worked(1);
}
}
public synchronized void clearSocket() throws IOException {
long maxTime = System.currentTimeMillis() + (1000 * 50); //50 secs timeout
while (System.currentTimeMillis() < maxTime) { //clear until we get no message and timeout is not elapsed
byte[] b = new byte[AbstractShell.BUFFER_SIZE];
if (this.socketToRead != null) {
this.socketToRead.getInputStream().read(b);
String s = new String(b);
s = s.replaceAll((char) 0 + "", ""); //python sends this char as payload.
if (s.length() == 0) {
return;
}
} else {
//if we have no socket, simply return (nothing to clear)
return;
}
}
}
/**
* @param operation
* @return
* @throws IOException
*/
public synchronized String read(IProgressMonitor monitor) throws IOException {
if (finishedForGood) {
throw new RuntimeException(
"Shells are already finished for good, so, it is an invalid state to try to read from it.");
}
if (inStart) {
throw new RuntimeException(
"The shell is still not completely started, so, it is an invalid state to try to read from it..");
}
if (!isConnected) {
throw new RuntimeException(
"The shell is still not connected, so, it is an invalid state to try to read from it..");
}
if (isInRead) {
throw new RuntimeException(
"The shell is already in read mode, so, it is an invalid state to try to read from it..");
}
if (isInWrite) {
throw new RuntimeException(
"The shell is already in write mode, so, it is an invalid state to try to read from it..");
}
isInRead = true;
try {
StringBuffer str = new StringBuffer();
int j = 0;
while (j < 200) {
byte[] b = new byte[AbstractShell.BUFFER_SIZE];
this.socketToRead.getInputStream().read(b);
String s = new String(b);
//processing without any status to present to the user
if (s.indexOf("@@PROCESSING_END@@") != -1) { //each time we get a processing message, reset j to 0.
s = s.replaceAll("@@PROCESSING_END@@", "");
j = 0;
communicateWork("Processing...", monitor);
}
//processing with some kind of status
if (s.indexOf("@@PROCESSING:") != -1) { //each time we get a processing message, reset j to 0.
s = s.replaceAll("@@PROCESSING:", "");
s = s.replaceAll("END@@", "");
j = 0;
s = URLDecoder.decode(s, ENCODING_UTF_8);
if (s.trim().equals("") == false) {
communicateWork("Processing: " + s, monitor);
} else {
communicateWork("Processing...", monitor);
}
s = "";
}
s = s.replaceAll((char) 0 + "", ""); //python sends this char as payload.
str.append(s);
if (str.indexOf("END@@") != -1) {
break;
} else {
if (s.length() == 0) { //only raise if nothing was received.
j++;
} else {
j = 0; //we are receiving, even though that may take a long time if the namespace is really polluted...
}
sleepALittle(10);
}
}
String ret = str.toString().replaceFirst("@@COMPLETIONS", "");
//remove END@@
try {
if (ret.indexOf("END@@") != -1) {
ret = ret.substring(0, ret.indexOf("END@@"));
return ret;
} else {
throw new RuntimeException("Couldn't find END@@ on received string.");
}
} catch (RuntimeException e) {
if (ret.length() > 500) {
ret = ret.substring(0, 499) + "...(continued)...";//if the string gets too big, it can crash Eclipse...
}
Log.log(IStatus.ERROR, ("ERROR WITH STRING:" + ret), e);
return "";
}
} finally {
isInRead = false;
}
}
/**
* @return s string with the contents read.
* @throws IOException
*/
protected synchronized String read() throws IOException {
String r = read(null);
//System.out.println("RETURNING:"+URLDecoder.decode(URLDecoder.decode(r,ENCODING_UTF_8),ENCODING_UTF_8));
return r;
}
/**
* @param str
* @throws IOException
*/
public synchronized void write(String str) throws IOException {
if (finishedForGood) {
throw new RuntimeException(
"Shells are already finished for good, so, it is an invalid state to try to write to it.");
}
if (inStart) {
throw new RuntimeException(
"The shell is still not completely started, so, it is an invalid state to try to write to it.");
}
if (!isConnected) {
throw new RuntimeException(
"The shell is still not connected, so, it is an invalid state to try to write to it.");
}
if (isInRead) {
throw new RuntimeException(
"The shell is already in read mode, so, it is an invalid state to try to write to it.");
}
if (isInWrite) {
throw new RuntimeException(
"The shell is already in write mode, so, it is an invalid state to try to write to it.");
}
isInWrite = true;
//dbg("WRITING:"+str);
try {
OutputStream outputStream = this.socketToWrite.getOutputStream();
outputStream.write(str.getBytes());
outputStream.flush();
} finally {
isInWrite = false;
}
}
/**
* @throws IOException
*/
private synchronized void closeConn() throws IOException {
//let's not send a message... just close the sockets and kill it
// try {
// write("@@KILL_SERVER_END@@");
// } catch (Exception e) {
// }
try {
if (socketToWrite != null) {
socketToWrite.close();
}
} catch (Exception e) {
}
socketToWrite = null;
try {
if (socketToRead != null) {
socketToRead.close();
}
} catch (Exception e) {
}
socketToRead = null;
try {
if (serverSocket != null) {
serverSocket.close();
}
} catch (Exception e) {
}
serverSocket = null;
}
/**
* this function should be used with care... it only destroys our processes without closing the
* connections correctly (intended for shutdowns)
*/
public synchronized void shutdown() {
socketToRead = null;
socketToWrite = null;
serverSocket = null;
if (process != null) {
process.destroy();
process = null;
}
}
/**
* Kill our sub-process.
* @throws IOException
*/
public synchronized void endIt() {
try {
closeConn();
} catch (Exception e) {
//that's ok...
}
//set that we are still not connected
isConnected = false;
if (process != null) {
process.destroy();
process = null;
}
}
/**
* @return list with tuples: new String[]{token, description}
* @throws CoreException
*/
public synchronized Tuple<String, List<String[]>> getImportCompletions(String str, List<String> pythonpath)
throws CoreException {
while (isInOperation) {
sleepALittle(25);
}
isInOperation = true;
try {
internalChangePythonPath(pythonpath);
try {
str = URLEncoder.encode(str, ENCODING_UTF_8);
return this.getTheCompletions("@@IMPORTS:" + str + "\nEND@@");
} catch (Exception e) {
throw new RuntimeException(e);
}
} finally {
isInOperation = false;
}
}
/**
* @param pythonpath
* @throws CoreException
*/
public synchronized void changePythonPath(List<String> pythonpath) throws CoreException {
while (isInOperation) {
sleepALittle(25);
}
isInOperation = true;
try {
internalChangePythonPath(pythonpath);
} finally {
isInOperation = false;
}
}
/**
* @param pythonpath
*/
private void internalChangePythonPath(List<String> pythonpath) {
if (finishedForGood) {
throw new RuntimeException(
"Shells are already finished for good, so, it is an invalid state to try to change its dir.");
}
StringBuffer buffer = new StringBuffer();
for (Iterator<String> iter = pythonpath.iterator(); iter.hasNext();) {
String path = iter.next();
buffer.append(path);
buffer.append("|");
}
try {
getTheCompletions("@@CHANGE_PYTHONPATH:" + URLEncoder.encode(buffer.toString(), ENCODING_UTF_8) + "\nEND@@");
} catch (Exception e) {
throw new RuntimeException(e);
}
}
protected synchronized Tuple<String, List<String[]>> getTheCompletions(String str) throws CoreException {
try {
this.write(str);
return getCompletions();
} catch (NullPointerException e) {
//still not started...
restartShell();
return getInvalidCompletion();
} catch (Exception e) {
if (DebugSettings.DEBUG_CODE_COMPLETION) {
Log.log(IStatus.ERROR, "ERROR getting completions.", e);
}
restartShell();
return getInvalidCompletion();
}
}
/**
* @throws CoreException
*
*/
public synchronized void restartShell() throws CoreException {
if (!isInRestart) {// we don't want to end up in a loop here...
isInRestart = true;
try {
if (finishedForGood) {
throw new RuntimeException(
"Shells are already finished for good, so, it is an invalid state to try to restart a new shell.");
}
try {
this.endIt();
} catch (Exception e) {
}
try {
synchronized (this) {
this.startIt(shellInterpreter, shellMillis);
}
} catch (Exception e) {
Log.log(IStatus.ERROR, "ERROR restarting shell.", e);
}
} finally {
isInRestart = false;
}
}
}
/**
* @return
*/
protected synchronized Tuple<String, List<String[]>> getInvalidCompletion() {
List<String[]> l = new ArrayList<String[]>();
return new Tuple<String, List<String[]>>(null, l);
}
/**
* @throws IOException
*/
protected synchronized Tuple<String, List<String[]>> getCompletions() throws IOException {
ArrayList<String[]> list = new ArrayList<String[]>();
String read = this.read();
String string = read.replaceAll("\\(", "").replaceAll("\\)", "");
StringTokenizer tokenizer = new StringTokenizer(string, ",");
//the first token is always the file for the module (no matter what)
String file = "";
if (tokenizer.hasMoreTokens()) {
file = URLDecoder.decode(tokenizer.nextToken(), ENCODING_UTF_8);
while (tokenizer.hasMoreTokens()) {
String token = URLDecoder.decode(tokenizer.nextToken(), ENCODING_UTF_8);
if (!tokenizer.hasMoreTokens()) {
return new Tuple<String, List<String[]>>(file, list);
}
String description = URLDecoder.decode(tokenizer.nextToken(), ENCODING_UTF_8);
String args = "";
if (tokenizer.hasMoreTokens()) {
args = URLDecoder.decode(tokenizer.nextToken(), ENCODING_UTF_8);
}
String type = TYPE_UNKNOWN_STR;
if (tokenizer.hasMoreTokens()) {
type = URLDecoder.decode(tokenizer.nextToken(), ENCODING_UTF_8);
}
//dbg(token);
//dbg(description);
if (!token.equals("ERROR:")) {
list.add(new String[] { token, description, args, type });
} else {
if (DebugSettings.DEBUG_CODE_COMPLETION) {
Log.addLogLevel();
try {
Log.toLogFile("Code completion shell error:", AbstractShell.class);
Log.toLogFile(token, AbstractShell.class);
Log.toLogFile(description, AbstractShell.class);
Log.toLogFile(args, AbstractShell.class);
Log.toLogFile(type, AbstractShell.class);
} finally {
Log.remLogLevel();
}
}
}
}
}
return new Tuple<String, List<String[]>>(file, list);
}
/**
* @param moduleName the name of the module where the token is defined
* @param token the token we are looking for
* @return the file where the token was defined, its line and its column (or null if it was not found)
*/
public synchronized Tuple<String[], int[]> getLineCol(String moduleName, String token, List<String> pythonpath) {
while (isInOperation) {
sleepALittle(25);
}
isInOperation = true;
try {
String str = moduleName + "." + token;
internalChangePythonPath(pythonpath);
try {
str = URLEncoder.encode(str, ENCODING_UTF_8);
Tuple<String, List<String[]>> theCompletions = this.getTheCompletions("@@SEARCH" + str + "\nEND@@");
List<String[]> def = theCompletions.o2;
if (def.size() == 0) {
return null;
}
String[] comps = def.get(0);
if (comps.length == 0) {
return null;
}
int line = Integer.parseInt(comps[0]);
int col = Integer.parseInt(comps[1]);
String foundAs = comps[2];
return new Tuple<String[], int[]>(new String[] { theCompletions.o1, foundAs }, new int[] { line, col });
} catch (Exception e) {
throw new RuntimeException(e);
}
} finally {
isInOperation = false;
}
}
}