package liveplugin.pluginrunner;
import com.intellij.openapi.application.PathManager;
import com.intellij.openapi.util.io.FileUtil;
import com.intellij.util.Function;
import com.intellij.util.PathUtil;
import liveplugin.MyFileUtil;
import scala.Some;
import scala.package$;
import scala.tools.nsc.Settings;
import scala.tools.nsc.interpreter.IMain;
import scala.tools.nsc.interpreter.Results;
import scala.tools.nsc.settings.MutableSettings;
import scala.xml.Null;
import java.io.File;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import static com.intellij.openapi.application.PathManager.getLibPath;
import static com.intellij.openapi.application.PathManager.getPluginsPath;
import static com.intellij.openapi.util.text.StringUtil.join;
import static com.intellij.util.containers.ContainerUtil.flatten;
import static com.intellij.util.containers.ContainerUtil.map;
import static com.intellij.util.containers.ContainerUtilRt.newArrayList;
import static java.io.File.pathSeparator;
import static java.util.Arrays.asList;
import static liveplugin.MyFileUtil.*;
import static liveplugin.pluginrunner.PluginRunner.ClasspathAddition.*;
/**
* This class should not be loaded unless scala libs are on classpath.
*/
public class ScalaPluginRunner implements PluginRunner {
private static final String SCALA_DEPENDS_ON_PLUGIN_KEYWORD = "// " + DEPENDS_ON_PLUGIN_KEYWORD;
public static final String MAIN_SCRIPT = "plugin.scala";
private static final String SCALA_ADD_TO_CLASSPATH_KEYWORD = "// " + ADD_TO_CLASSPATH_KEYWORD;
private static final StringWriter interpreterOutput = new StringWriter();
private static final Object interpreterLock = new Object();
private final ErrorReporter errorReporter;
private final Map<String, String> environment;
public ScalaPluginRunner(ErrorReporter errorReporter, Map<String, String> environment) {
this.errorReporter = errorReporter;
this.environment = environment;
}
private static IMain initInterpreter(String interpreterClasspath, ClassLoader parentClassLoader) throws ClassNotFoundException {
Settings settings = new Settings();
MutableSettings.PathSetting bootClasspath = (MutableSettings.PathSetting) settings.bootclasspath();
bootClasspath.append(interpreterClasspath);
settings.explicitParentLoader_$eq(new Some<>(parentClassLoader));
((MutableSettings.BooleanSetting) settings.usejavacp()).tryToSetFromPropertyValue("true");
return new IMain(settings, new PrintWriter(interpreterOutput));
}
private static String createInterpreterClasspath(List<String> additionalPaths) throws ClassNotFoundException {
Function<File, String> toAbsolutePath = it -> it.getAbsolutePath();
Function<File, Collection<File>> findPluginJars = pluginPath -> {
if (pluginPath.isFile()) {
return newArrayList(pluginPath);
} else {
return newArrayList(withDefault(new File[0], new File(pluginPath, "lib").listFiles((file, fileName) -> fileName.endsWith(".jar") || fileName.endsWith(".zip"))));
}
};
String compilerPath = PathUtil.getJarPathForClass(Class.forName("scala.tools.nsc.Interpreter"));
String scalaLibPath = PathUtil.getJarPathForClass(Class.forName("scala.Some"));
String intellijLibPath = join(map(withDefault(new File[0], new File(getLibPath()).listFiles()), toAbsolutePath), pathSeparator);
String allNonCorePluginsPath = join(map(flatten(map(withDefault(new File[0], new File(getPluginsPath()).listFiles()), findPluginJars)), toAbsolutePath), pathSeparator);
String livePluginPath = PathManager.getResourceRoot(ScalaPluginRunner.class, "/liveplugin/"); // this is only useful when running liveplugin from IDE (it's not packed into jar)
return join(asList(compilerPath, scalaLibPath, livePluginPath, intellijLibPath, allNonCorePluginsPath), pathSeparator) +
pathSeparator + join(additionalPaths, pathSeparator);
}
private static <T> T withDefault(T defaultValue, T value) {
return value == null ? defaultValue : value;
}
@Override public boolean canRunPlugin(String pathToPluginFolder) {
return findScriptFileIn(pathToPluginFolder, MAIN_SCRIPT) != null;
}
@Override public void runPlugin(String pathToPluginFolder, final String pluginId,
Map<String, ?> binding, Function<Runnable, Void> runOnEDTCallback) {
final File scriptFile = MyFileUtil.findScriptFileIn(pathToPluginFolder, ScalaPluginRunner.MAIN_SCRIPT);
assert scriptFile != null;
final IMain interpreter;
synchronized (interpreterLock) {
try {
environment.put("PLUGIN_PATH", pathToPluginFolder);
List<String> dependentPlugins = findPluginDependencies(readLines(asUrl(scriptFile)), SCALA_DEPENDS_ON_PLUGIN_KEYWORD);
List<String> additionalPaths = findClasspathAdditions(readLines(asUrl(scriptFile)), SCALA_ADD_TO_CLASSPATH_KEYWORD, environment, path -> {
errorReporter.addLoadingError(pluginId, "Couldn't find dependency '" + path + "'");
return null;
});
String classpath = createInterpreterClasspath(additionalPaths);
ClassLoader parentClassLoader = createParentClassLoader(dependentPlugins, pluginId, errorReporter);
interpreter = initInterpreter(classpath, parentClassLoader);
} catch (Exception | LinkageError e) {
errorReporter.addLoadingError("Failed to init scala interpreter", e);
return;
}
interpreterOutput.getBuffer().delete(0, interpreterOutput.getBuffer().length());
for (Map.Entry<String, ?> entry : binding.entrySet()) {
String key = entry.getKey();
Object value = entry.getValue();
String valueClassName = value == null ? Null.class.getCanonicalName() : value.getClass().getCanonicalName();
interpreter.bind(key, valueClassName, value, package$.MODULE$.List().empty());
}
}
runOnEDTCallback.fun(() -> {
synchronized (interpreterLock) {
Results.Result result;
try {
result = interpreter.interpret(FileUtil.loadFile(scriptFile));
} catch (LinkageError e) {
errorReporter.addLoadingError(pluginId, "Error linking script file: " + scriptFile);
return;
} catch (Exception e) {
errorReporter.addLoadingError(pluginId, "Error reading script file: " + scriptFile);
return;
}
if (!(result instanceof Results.Success$)) {
errorReporter.addRunningError(pluginId, interpreterOutput.toString());
}
}
});
}
@Override public String scriptName() {
return MAIN_SCRIPT;
}
}