/*
* Copyright 2010-2016 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.idea.run;
import com.intellij.diagnostic.logging.LogConfigurationPanel;
import com.intellij.execution.*;
import com.intellij.execution.application.BaseJavaApplicationCommandLineState;
import com.intellij.execution.configuration.EnvironmentVariablesComponent;
import com.intellij.execution.configurations.*;
import com.intellij.execution.runners.ExecutionEnvironment;
import com.intellij.execution.util.JavaParametersUtil;
import com.intellij.execution.util.ProgramParametersUtil;
import com.intellij.openapi.components.PathMacroManager;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.module.ModuleManager;
import com.intellij.openapi.module.ModuleUtilCore;
import com.intellij.openapi.options.SettingsEditor;
import com.intellij.openapi.options.SettingsEditorGroup;
import com.intellij.openapi.roots.*;
import com.intellij.openapi.util.DefaultJDOMExternalizer;
import com.intellij.openapi.util.InvalidDataException;
import com.intellij.openapi.util.WriteExternalException;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.PsiClass;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiMethod;
import com.intellij.psi.PsiPackage;
import com.intellij.psi.search.GlobalSearchScope;
import com.intellij.refactoring.listeners.RefactoringElementAdapter;
import com.intellij.refactoring.listeners.RefactoringElementListener;
import kotlin.collections.ArraysKt;
import kotlin.collections.CollectionsKt;
import kotlin.jvm.functions.Function1;
import org.jdom.Element;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.kotlin.asJava.classes.KtLightClassForSourceDeclaration;
import org.jetbrains.kotlin.asJava.elements.KtLightMethod;
import org.jetbrains.kotlin.idea.MainFunctionDetector;
import org.jetbrains.kotlin.idea.caches.resolve.ResolutionUtils;
import org.jetbrains.kotlin.name.FqName;
import org.jetbrains.kotlin.psi.KtDeclaration;
import org.jetbrains.kotlin.psi.KtDeclarationContainer;
import org.jetbrains.kotlin.psi.KtNamedFunction;
import org.jetbrains.kotlin.resolve.BindingContext;
import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode;
import java.util.*;
public class JetRunConfiguration extends ModuleBasedConfiguration<RunConfigurationModule>
implements CommonJavaRunConfigurationParameters, RefactoringListenerProvider {
public String MAIN_CLASS_NAME;
public String VM_PARAMETERS;
public String PROGRAM_PARAMETERS;
public String WORKING_DIRECTORY;
public boolean ALTERNATIVE_JRE_PATH_ENABLED;
public String ALTERNATIVE_JRE_PATH;
private Map<String, String> myEnvs = new LinkedHashMap<String, String>();
public boolean PASS_PARENT_ENVS = true;
public JetRunConfiguration(String name, RunConfigurationModule runConfigurationModule, ConfigurationFactory factory) {
super(name, runConfigurationModule, factory);
runConfigurationModule.init();
}
@Override
public Collection<Module> getValidModules() {
return Arrays.asList(ModuleManager.getInstance(getProject()).getModules());
}
@Nullable
@Override
public GlobalSearchScope getSearchScope() {
return SearchScopeProvider.createSearchScope(getModules());
}
@NotNull
@Override
public SettingsEditor<? extends RunConfiguration> getConfigurationEditor() {
SettingsEditorGroup<JetRunConfiguration> group = new SettingsEditorGroup<JetRunConfiguration>();
group.addEditor(ExecutionBundle.message("run.configuration.configuration.tab.title"), new JetRunConfigurationEditor(getProject()));
JavaRunConfigurationExtensionManager.getInstance().appendEditors(this, group);
group.addEditor(ExecutionBundle.message("logs.tab.title"), new LogConfigurationPanel<JetRunConfiguration>());
return group;
}
@Override
public void readExternal(Element element) throws InvalidDataException {
PathMacroManager.getInstance(getProject()).expandPaths(element);
super.readExternal(element);
JavaRunConfigurationExtensionManager.getInstance().readExternal(this, element);
DefaultJDOMExternalizer.readExternal(this, element);
readModule(element);
EnvironmentVariablesComponent.readExternal(element, getEnvs());
}
@Override
public void writeExternal(Element element) throws WriteExternalException {
super.writeExternal(element);
JavaRunConfigurationExtensionManager.getInstance().writeExternal(this, element);
DefaultJDOMExternalizer.writeExternal(this, element);
writeModule(element);
EnvironmentVariablesComponent.writeExternal(element, getEnvs());
PathMacroManager.getInstance(getProject()).collapsePathsRecursively(element);
}
@Override
public void setVMParameters(String value) {
VM_PARAMETERS = value;
}
@Override
public String getVMParameters() {
return VM_PARAMETERS;
}
@Override
public void setProgramParameters(String value) {
PROGRAM_PARAMETERS = value;
}
@Override
public String getProgramParameters() {
return PROGRAM_PARAMETERS;
}
@Override
public void setWorkingDirectory(String value) {
WORKING_DIRECTORY = ExternalizablePath.urlValue(value);
}
@Override
public String getWorkingDirectory() {
return ExternalizablePath.localPathValue(WORKING_DIRECTORY);
}
@Override
public void setPassParentEnvs(boolean passParentEnvs) {
PASS_PARENT_ENVS = passParentEnvs;
}
@Override
@NotNull
public Map<String, String> getEnvs() {
return myEnvs;
}
@Override
public void setEnvs(@NotNull Map<String, String> envs) {
this.myEnvs = envs;
}
@Override
public boolean isPassParentEnvs() {
return PASS_PARENT_ENVS;
}
@Override
public String getRunClass() {
return MAIN_CLASS_NAME;
}
public void setRunClass(String value) {
MAIN_CLASS_NAME = value;
}
@Override
public String getPackage() {
return null;
}
@Override
public boolean isAlternativeJrePathEnabled() {
return ALTERNATIVE_JRE_PATH_ENABLED;
}
@Override
public void setAlternativeJrePathEnabled(boolean enabled) {
ALTERNATIVE_JRE_PATH_ENABLED = enabled;
}
@Override
public String getAlternativeJrePath() {
return ALTERNATIVE_JRE_PATH;
}
@Override
public void setAlternativeJrePath(String path) {
ALTERNATIVE_JRE_PATH = path;
}
@Override
public void checkConfiguration() throws RuntimeConfigurationException {
JavaParametersUtil.checkAlternativeJRE(this);
ProgramParametersUtil.checkWorkingDirectoryExist(this, getProject(), getConfigurationModule().getModule());
JavaRunConfigurationExtensionManager.checkConfigurationIsValid(this);
Module module = getConfigurationModule().getModule();
if (module == null) {
throw new RuntimeConfigurationError("Module not specified");
}
if (StringUtil.isEmpty(MAIN_CLASS_NAME)) {
throw new RuntimeConfigurationError("No main class specified");
}
PsiClass psiClass = JavaExecutionUtil.findMainClass(module, MAIN_CLASS_NAME);
if (psiClass == null) {
throw new RuntimeConfigurationWarning("Class '" + MAIN_CLASS_NAME + "' not found in module " + getConfigurationModule().getModuleName());
}
if (findMainFun(psiClass) == null) {
throw new RuntimeConfigurationWarning("The class " + MAIN_CLASS_NAME + " has no main method");
}
}
@Override
public RunProfileState getState(@NotNull Executor executor, @NotNull ExecutionEnvironment executionEnvironment)
throws ExecutionException {
return new MyJavaCommandLineState(this, executionEnvironment);
}
@Nullable
@Override
public RefactoringElementListener getRefactoringElementListener(PsiElement element) {
String fqNameBeingRenamed;
if (element instanceof KtDeclarationContainer) {
fqNameBeingRenamed = KotlinRunConfigurationProducer.Companion.getStartClassFqName((KtDeclarationContainer) element);
}
else if (element instanceof PsiPackage) {
fqNameBeingRenamed = ((PsiPackage) element).getQualifiedName();
}
else {
fqNameBeingRenamed = null;
}
if (fqNameBeingRenamed == null ||
!MAIN_CLASS_NAME.equals(fqNameBeingRenamed) && !MAIN_CLASS_NAME.startsWith(fqNameBeingRenamed + ".")) {
return null;
}
if (element instanceof KtDeclarationContainer) {
return new RefactoringElementAdapter() {
@Override
public void undoElementMovedOrRenamed(@NotNull PsiElement newElement, @NotNull String oldQualifiedName) {
updateMainClassName(newElement);
}
@Override
protected void elementRenamedOrMoved(@NotNull PsiElement newElement) {
updateMainClassName(newElement);
}
};
}
final String nameSuffix = MAIN_CLASS_NAME.substring(fqNameBeingRenamed.toString().length());
return new RefactoringElementAdapter() {
@Override
protected void elementRenamedOrMoved(@NotNull PsiElement newElement) {
updateMainClassNameWithSuffix(newElement, nameSuffix);
}
@Override
public void undoElementMovedOrRenamed(@NotNull PsiElement newElement, @NotNull String oldQualifiedName) {
updateMainClassNameWithSuffix(newElement, nameSuffix);
}
};
}
private void updateMainClassName(PsiElement element) {
KtDeclarationContainer container = KotlinRunConfigurationProducer.Companion.getEntryPointContainer(element);
String name = KotlinRunConfigurationProducer.Companion.getStartClassFqName(container);
if (name != null) {
MAIN_CLASS_NAME = name;
}
}
private void updateMainClassNameWithSuffix(PsiElement element, String suffix) {
if (element instanceof PsiPackage) {
MAIN_CLASS_NAME = ((PsiPackage) element).getQualifiedName() + suffix;
}
}
@Override
public String suggestedName() {
if (StringUtil.isEmpty(MAIN_CLASS_NAME)) {
return null;
}
return MAIN_CLASS_NAME;
}
@NotNull
private static Collection<KtNamedFunction> getMainFunCandidates(@NotNull PsiClass psiClass) {
return CollectionsKt.filterNotNull(
ArraysKt.map(
psiClass.getAllMethods(),
new Function1<PsiMethod, KtNamedFunction>() {
@Override
public KtNamedFunction invoke(PsiMethod method) {
if (!(method instanceof KtLightMethod)) return null;
if (!method.getName().equals("main")) return null;
KtDeclaration declaration = ((KtLightMethod) method).getKotlinOrigin();
return declaration instanceof KtNamedFunction ? (KtNamedFunction) declaration : null;
}
}
)
);
}
@Nullable
private static KtNamedFunction findMainFun(@NotNull PsiClass psiClass) {
for (KtNamedFunction function : getMainFunCandidates(psiClass)) {
BindingContext bindingContext = ResolutionUtils.analyze(function, BodyResolveMode.FULL);
MainFunctionDetector mainFunctionDetector = new MainFunctionDetector(bindingContext);
if (mainFunctionDetector.isMain(function)) return function;
}
return null;
}
private static class MyJavaCommandLineState extends BaseJavaApplicationCommandLineState<JetRunConfiguration> {
public MyJavaCommandLineState(@NotNull JetRunConfiguration configuration, ExecutionEnvironment environment) {
super(environment, configuration);
}
@Override
protected JavaParameters createJavaParameters() throws ExecutionException {
JavaParameters params = new JavaParameters();
RunConfigurationModule module = myConfiguration.getConfigurationModule();
int classPathType = getClasspathType(module);
String jreHome = myConfiguration.ALTERNATIVE_JRE_PATH_ENABLED ? myConfiguration.ALTERNATIVE_JRE_PATH : null;
JavaParametersUtil.configureModule(module, params, classPathType, jreHome);
setupJavaParameters(params);
params.setMainClass(myConfiguration.getRunClass());
return params;
}
private int getClasspathType(RunConfigurationModule configurationModule) throws CantRunException {
Module module = configurationModule.getModule();
if (module == null) throw CantRunException.noModuleConfigured(configurationModule.getModuleName());
String runClass = myConfiguration.getRunClass();
if (runClass == null) throw new CantRunException(String.format("Run class should be defined for configuration '%s'", myConfiguration.getName()));
PsiClass psiClass = JavaExecutionUtil.findMainClass(module, runClass);
if (psiClass == null) throw CantRunException.classNotFound(runClass, module);
KtNamedFunction mainFun = findMainFun(psiClass);
if (mainFun == null) throw new CantRunException(noFunctionFoundMessage(psiClass));
Module classModule = ModuleUtilCore.findModuleForPsiElement(mainFun);
if (classModule == null) classModule = module;
VirtualFile virtualFileForMainFun = mainFun.getContainingFile().getVirtualFile();
if (virtualFileForMainFun == null) throw new CantRunException(noFunctionFoundMessage(psiClass));
ModuleFileIndex fileIndex = ModuleRootManager.getInstance(classModule).getFileIndex();
if (fileIndex.isInSourceContent(virtualFileForMainFun)) {
if (fileIndex.isInTestSourceContent(virtualFileForMainFun)) {
return JavaParameters.JDK_AND_CLASSES_AND_TESTS;
}
else {
return JavaParameters.JDK_AND_CLASSES;
}
}
List<OrderEntry> entriesForFile = fileIndex.getOrderEntriesForFile(virtualFileForMainFun);
for (OrderEntry entry : entriesForFile) {
if (entry instanceof ExportableOrderEntry && ((ExportableOrderEntry)entry).getScope() == DependencyScope.TEST) {
return JavaParameters.JDK_AND_CLASSES_AND_TESTS;
}
}
return JavaParameters.JDK_AND_CLASSES;
}
@NotNull
private String noFunctionFoundMessage(@NotNull PsiClass psiClass) {
//noinspection ConstantConditions
FqName classFqName = new FqName(psiClass.getQualifiedName());
if (psiClass instanceof KtLightClassForSourceDeclaration) {
return String.format("Function 'main' not found in class '%s'", classFqName);
}
return String.format("Top-level function 'main' not found in package '%s'", classFqName.parent());
}
}
}