diff --git a/idea/src/org/jetbrains/jet/plugin/configuration/ConfigureKotlinInProjectUtils.java b/idea/src/org/jetbrains/jet/plugin/configuration/ConfigureKotlinInProjectUtils.java index aa6ab2a9264..f500b1b0426 100644 --- a/idea/src/org/jetbrains/jet/plugin/configuration/ConfigureKotlinInProjectUtils.java +++ b/idea/src/org/jetbrains/jet/plugin/configuration/ConfigureKotlinInProjectUtils.java @@ -168,4 +168,8 @@ public class ConfigureKotlinInProjectUtils { private ConfigureKotlinInProjectUtils() { } + + public static void showInfoNotification(@NotNull String message) { + Notifications.Bus.notify(new Notification("Configure Kotlin", "Configure Kotlin", message, NotificationType.INFORMATION)); + } } diff --git a/idea/src/org/jetbrains/jet/plugin/configuration/KotlinWithGradleConfigurator.java b/idea/src/org/jetbrains/jet/plugin/configuration/KotlinWithGradleConfigurator.java new file mode 100644 index 00000000000..89cb741f915 --- /dev/null +++ b/idea/src/org/jetbrains/jet/plugin/configuration/KotlinWithGradleConfigurator.java @@ -0,0 +1,303 @@ +/* + * Copyright 2010-2013 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.jet.plugin.configuration; + +import com.intellij.codeInsight.CodeInsightUtilCore; +import com.intellij.openapi.application.Result; +import com.intellij.openapi.command.WriteCommandAction; +import com.intellij.openapi.module.Module; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.ui.Messages; +import com.intellij.openapi.vfs.VfsUtil; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.openapi.vfs.WritingAccessProvider; +import com.intellij.psi.PsiElement; +import com.intellij.psi.PsiFile; +import com.intellij.psi.PsiManager; +import com.intellij.psi.codeStyle.CodeStyleManager; +import com.intellij.psi.util.PsiTreeUtil; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.jet.plugin.framework.ui.ConfigureDialogWithModulesAndVersion; +import org.jetbrains.plugins.gradle.util.GradleConstants; +import org.jetbrains.plugins.groovy.lang.psi.GroovyFile; +import org.jetbrains.plugins.groovy.lang.psi.GroovyPsiElementFactory; +import org.jetbrains.plugins.groovy.lang.psi.api.statements.GrStatement; +import org.jetbrains.plugins.groovy.lang.psi.api.statements.blocks.GrClosableBlock; +import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrApplicationStatement; +import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrExpression; +import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.path.GrMethodCallExpression; +import org.jetbrains.plugins.groovy.lang.psi.api.util.GrStatementOwner; + +import java.io.File; +import java.util.List; + +import static org.jetbrains.jet.plugin.configuration.ConfigureKotlinInProjectUtils.showInfoNotification; + +public abstract class KotlinWithGradleConfigurator implements KotlinProjectConfigurator { + private static final String[] KOTLIN_VERSIONS = {"0.6.+", "0.1-SNAPSHOT"}; + + protected static final String VERSION_TEMPLATE = "$VERSION$"; + + protected static final String CLASSPATH = "classpath \"org.jetbrains.kotlin:kotlin-gradle-plugin:$ext.kotlin_version\""; + protected static final String SNAPSHOT_REPOSITORY = "maven {\nurl 'http://oss.sonatype.org/content/repositories/snapshots'\n}"; + protected static final String REPOSITORY = "mavenCentral()\n"; + protected static final String LIBRARY = "compile \"org.jetbrains.kotlin:kotlin-stdlib:$ext.kotlin_version\""; + protected static final String SOURCE_SET = "main.java.srcDirs += 'src/main/kotlin'\n"; + protected static final String VERSION = String.format("ext.kotlin_version = '%s'", VERSION_TEMPLATE); + + @Override + public boolean isConfigured(@NotNull Module module) { + GroovyFile moduleGradleFile = getBuildGradleFile(module.getProject(), getDefaultPathToBuildGradleFile(module)); + if (moduleGradleFile != null && isFileConfigured(moduleGradleFile)) { + return true; + } + GroovyFile projectGradleFile = getBuildGradleFile(module.getProject(), getDefaultPathToBuildGradleFile(module.getProject())); + return projectGradleFile != null && isFileConfigured(projectGradleFile); + } + + private boolean isFileConfigured(GroovyFile projectGradleFile) { + String fileText = projectGradleFile.getText(); + return fileText.contains(getApplyPluginDirective()) && fileText.contains(LIBRARY); + } + + @Override + public void configure(Project project) { + List nonConfiguredModules = ConfigureKotlinInProjectUtils.getNonConfiguredModules(project, this); + + ConfigureDialogWithModulesAndVersion dialog = + new ConfigureDialogWithModulesAndVersion(project, nonConfiguredModules, KOTLIN_VERSIONS); + + dialog.show(); + if (!dialog.isOK()) return; + + for (Module module : dialog.getModulesToConfigure()) { + String gradleFilePath = getDefaultPathToBuildGradleFile(module); + GroovyFile file = getBuildGradleFile(project, gradleFilePath); + if (file != null && canConfigureFile(file)) { + changeGradleFile(file, dialog.getKotlinVersion()); + } + else { + showErrorMessage(project, "Cannot find build.gradle file for module " + module.getName()); + } + } + } + + private void addElements(@NotNull GroovyFile file, @NotNull String version) { + GrClosableBlock buildScriptBlock = getBuildScriptBlock(file); + addFirstExpressionInBlockIfNeeded(VERSION.replace(VERSION_TEMPLATE, version), buildScriptBlock); + + GrClosableBlock buildScriptRepositoriesBlock = getBuildScriptRepositoriesBlock(file); + if (isSnapshot(version)) { + addLastExpressionInBlockIfNeeded(SNAPSHOT_REPOSITORY, buildScriptRepositoriesBlock); + } + else if (!buildScriptRepositoriesBlock.getText().contains(REPOSITORY)) { + addLastExpressionInBlockIfNeeded(REPOSITORY, buildScriptRepositoriesBlock); + } + + GrClosableBlock buildScriptDependenciesBlock = getBuildScriptDependenciesBlock(file); + addLastExpressionInBlockIfNeeded(CLASSPATH, buildScriptDependenciesBlock); + + if (!file.getText().contains(getApplyPluginDirective())) { + GrExpression apply = GroovyPsiElementFactory.getInstance(file.getProject()).createExpressionFromText(getApplyPluginDirective()); + GrApplicationStatement applyStatement = getApplyStatement(file); + if (applyStatement != null) { + file.addAfter(apply, applyStatement); + } + else { + file.addAfter(apply, buildScriptBlock.getParent()); + } + } + + GrClosableBlock repositoriesBlock = getRepositoriesBlock(file); + if (isSnapshot(version)) { + addLastExpressionInBlockIfNeeded(SNAPSHOT_REPOSITORY, repositoriesBlock); + } + else if (!repositoriesBlock.getText().contains(REPOSITORY)) { + addLastExpressionInBlockIfNeeded(REPOSITORY, repositoriesBlock); + } + + GrClosableBlock dependenciesBlock = getDependenciesBlock(file); + addLastExpressionInBlockIfNeeded(LIBRARY, dependenciesBlock); + + addSourceSetsBlock(file); + } + + protected abstract String getApplyPluginDirective(); + + protected abstract void addSourceSetsBlock(@NotNull GroovyFile file); + + protected static boolean canConfigureFile(@NotNull GroovyFile file) { + return WritingAccessProvider.isPotentiallyWritable(file.getVirtualFile(), null); + } + + @Nullable + protected static GroovyFile getBuildGradleFile(Project project, String path) { + VirtualFile file = VfsUtil.findFileByIoFile(new File(path), true); + if (file == null) { + return null; + } + PsiFile psiFile = PsiManager.getInstance(project).findFile(file); + if (!(psiFile instanceof GroovyFile)) return null; + return (GroovyFile) psiFile; + } + + @NotNull + protected static String getDefaultPathToBuildGradleFile(@NotNull Project project) { + return project.getBasePath() + "/" + GradleConstants.DEFAULT_SCRIPT_NAME; + } + + @NotNull + protected static String getDefaultPathToBuildGradleFile(@NotNull Module module) { + return new File(module.getModuleFilePath()).getParent() + "/" + GradleConstants.DEFAULT_SCRIPT_NAME; + } + + protected static boolean isSnapshot(@NotNull String version) { + return version.contains("SNAPSHOT"); + } + + protected void changeGradleFile(@NotNull final GroovyFile groovyFile, @NotNull final String version) { + new WriteCommandAction(groovyFile.getProject()) { + @Override + protected void run(Result result) { + addElements(groovyFile, version); + + CodeInsightUtilCore.forcePsiPostprocessAndRestoreElement(groovyFile); + } + }.execute(); + + VirtualFile virtualFile = groovyFile.getVirtualFile(); + if (virtualFile != null) { + showInfoNotification(virtualFile.getPath() + " was modified"); + } + } + + @NotNull + protected static GrClosableBlock getDependenciesBlock(@NotNull GrStatementOwner file) { + return getBlockOrCreate(file, "dependencies"); + } + + @NotNull + protected static GrClosableBlock getSourceSetsBlock(@NotNull GrStatementOwner parent) { + return getBlockOrCreate(parent, "sourceSets"); + } + + @NotNull + protected static GrClosableBlock getBuildScriptBlock(@NotNull GrStatementOwner file) { + return getBlockOrCreate(file, "buildscript"); + } + + @NotNull + protected static GrClosableBlock getBuildScriptDependenciesBlock(@NotNull GrStatementOwner file) { + GrClosableBlock buildScript = getBuildScriptBlock(file); + return getBlockOrCreate(buildScript, "dependencies"); + } + + @NotNull + protected static GrClosableBlock getBuildScriptRepositoriesBlock(@NotNull GrStatementOwner file) { + GrClosableBlock buildScript = getBuildScriptBlock(file); + return getBlockOrCreate(buildScript, "repositories"); + } + + @NotNull + protected static GrClosableBlock getRepositoriesBlock(@NotNull GrStatementOwner file) { + return getBlockOrCreate(file, "repositories"); + } + + @NotNull + protected static GrClosableBlock getBlockOrCreate(@NotNull GrStatementOwner parent, @NotNull String name) { + GrClosableBlock block = getBlockByName(parent, name); + if (block == null) { + GroovyPsiElementFactory factory = GroovyPsiElementFactory.getInstance(parent.getProject()); + GrExpression newBlock = factory.createExpressionFromText(name + "{\n}\n"); + GrStatement[] statements = parent.getStatements(); + if (statements.length > 0) { + parent.addAfter(newBlock, statements[statements.length - 1]); + } + else { + parent.addAfter(newBlock, parent.getFirstChild()); + } + block = getBlockByName(parent, name); + assert block != null : "Block should be non-null because it is created"; + } + return block; + } + + protected static void addLastExpressionInBlockIfNeeded(@NotNull String text, @NotNull GrClosableBlock block) { + addExpressionInBlockIfNeeded(text, block, false); + } + + protected static void addFirstExpressionInBlockIfNeeded(@NotNull String text, @NotNull GrClosableBlock block) { + addExpressionInBlockIfNeeded(text, block, true); + } + + @Nullable + private static GrClosableBlock getBlockByName(@NotNull PsiElement parent, @NotNull String name) { + GrMethodCallExpression[] allExpressions = PsiTreeUtil.getChildrenOfType(parent, GrMethodCallExpression.class); + if (allExpressions != null) { + for (GrMethodCallExpression expression : allExpressions) { + GrExpression invokedExpression = expression.getInvokedExpression(); + if (invokedExpression == null) continue; + if (expression.getClosureArguments().length == 0) continue; + + String expressionText = invokedExpression.getText(); + if (expressionText.equals(name)) return expression.getClosureArguments()[0]; + } + } + return null; + } + + private static void addExpressionInBlockIfNeeded(@NotNull String text, @NotNull GrClosableBlock block, boolean isFirst) { + if (block.getText().contains(text)) return; + GrExpression newStatement = GroovyPsiElementFactory.getInstance(block.getProject()).createExpressionFromText(text); + CodeStyleManager.getInstance(block.getProject()).reformat(newStatement); + GrStatement[] statements = block.getStatements(); + if (!isFirst && statements.length > 0) { + GrStatement lastStatement = statements[statements.length - 1]; + if (lastStatement != null) { + block.addAfter(newStatement, lastStatement); + } + } + else { + PsiElement firstChild = block.getFirstChild(); + if (firstChild != null) { + block.addAfter(newStatement, firstChild); + } + } + } + + @Nullable + private static GrApplicationStatement getApplyStatement(@NotNull GroovyFile file) { + GrApplicationStatement[] applyStatement = PsiTreeUtil.getChildrenOfType(file, GrApplicationStatement.class); + if (applyStatement == null) return null; + for (GrApplicationStatement callExpression : applyStatement) { + GrExpression invokedExpression = callExpression.getInvokedExpression(); + if (invokedExpression != null && invokedExpression.getText().equals("apply")) { + return callExpression; + } + } + return null; + } + + protected static void showErrorMessage(@NotNull Project project, @Nullable String message) { + Messages.showErrorDialog(project, + "Couldn't configure kotlin-gradle plugin automatically.
" + + (message != null ? message : "") + + "See manual installation instructions here", + "Configure Kotlin-Gradle Plugin"); + } +} diff --git a/idea/src/org/jetbrains/jet/plugin/configuration/KotlinWithLibraryConfigurator.java b/idea/src/org/jetbrains/jet/plugin/configuration/KotlinWithLibraryConfigurator.java index ab85bed751f..2135aa7d2d3 100644 --- a/idea/src/org/jetbrains/jet/plugin/configuration/KotlinWithLibraryConfigurator.java +++ b/idea/src/org/jetbrains/jet/plugin/configuration/KotlinWithLibraryConfigurator.java @@ -1,7 +1,5 @@ package org.jetbrains.jet.plugin.configuration; -import com.intellij.notification.Notification; -import com.intellij.notification.NotificationType; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.module.Module; import com.intellij.openapi.project.Project; @@ -24,7 +22,7 @@ import org.jetbrains.jet.plugin.framework.ui.FileUIUtils; import java.io.File; -import static com.intellij.notification.Notifications.Bus; +import static org.jetbrains.jet.plugin.configuration.ConfigureKotlinInProjectUtils.showInfoNotification; public abstract class KotlinWithLibraryConfigurator implements KotlinProjectConfigurator { @@ -268,10 +266,6 @@ public abstract class KotlinWithLibraryConfigurator implements KotlinProjectConf return copy; } - private static void showInfoNotification(@NotNull String message) { - Bus.notify(new Notification("Configure Kotlin", "Configure Kotlin", message, NotificationType.INFORMATION)); - } - protected boolean needToChooseJarPath(@NotNull Project project) { String defaultPath = getDefaultPathToJarFile(project); return !isProjectLibraryPresent(project) && !isJarPresent(defaultPath); diff --git a/idea/src/org/jetbrains/jet/plugin/framework/ui/ConfigureDialogWithModulesAndVersion.form b/idea/src/org/jetbrains/jet/plugin/framework/ui/ConfigureDialogWithModulesAndVersion.form new file mode 100644 index 00000000000..da6140359b3 --- /dev/null +++ b/idea/src/org/jetbrains/jet/plugin/framework/ui/ConfigureDialogWithModulesAndVersion.form @@ -0,0 +1,56 @@ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
diff --git a/idea/src/org/jetbrains/jet/plugin/framework/ui/ConfigureDialogWithModulesAndVersion.java b/idea/src/org/jetbrains/jet/plugin/framework/ui/ConfigureDialogWithModulesAndVersion.java new file mode 100644 index 00000000000..77add0c32ff --- /dev/null +++ b/idea/src/org/jetbrains/jet/plugin/framework/ui/ConfigureDialogWithModulesAndVersion.java @@ -0,0 +1,70 @@ +/* + * Copyright 2010-2013 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.jet.plugin.framework.ui; + +import com.intellij.openapi.module.Module; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.ui.DialogWrapper; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import javax.swing.*; +import java.awt.*; +import java.util.List; + +public class ConfigureDialogWithModulesAndVersion extends DialogWrapper { + private final ChooseModulePanel chooseModulePanel; + + private JPanel contentPane; + private JPanel chooseModulesPanelPlace; + private JComboBox kotlinVersionComboBox; + + public ConfigureDialogWithModulesAndVersion( + @NotNull Project project, + @NotNull List modules, + @NotNull String[] kotlinVersions + ) { + super(project); + + setTitle("Configure Kotlin in Project"); + + init(); + + chooseModulePanel = new ChooseModulePanel(project, modules); + chooseModulesPanelPlace.add(chooseModulePanel.getContentPane(), BorderLayout.CENTER); + + chooseModulePanel.showSeparator(); + + for (String version : kotlinVersions) { + kotlinVersionComboBox.addItem(version); + } + } + + public List getModulesToConfigure() { + return chooseModulePanel.getModulesToConfigure(); + } + + public String getKotlinVersion() { + return (String) kotlinVersionComboBox.getSelectedItem(); + } + + @Nullable + @Override + protected JComponent createCenterPanel() { + return contentPane; + } +}