diff --git a/idea/src/META-INF/plugin.xml b/idea/src/META-INF/plugin.xml
index 1f6da57ebd9..0b9dfc45cc4 100644
--- a/idea/src/META-INF/plugin.xml
+++ b/idea/src/META-INF/plugin.xml
@@ -34,6 +34,9 @@
org.jetbrains.jet.plugin.ktSignature.KotlinSignatureInJavaMarkerUpdater
+
+ org.jetbrains.jet.plugin.configuration.ui.AbsentJdkAnnotationsNotifications
+
@@ -310,7 +313,6 @@
-
diff --git a/idea/src/org/jetbrains/jet/plugin/configuration/ConfigureKotlinNotificationManager.kt b/idea/src/org/jetbrains/jet/plugin/configuration/ConfigureKotlinNotificationManager.kt
index 37a68bb46b2..7cdf15b2a5b 100644
--- a/idea/src/org/jetbrains/jet/plugin/configuration/ConfigureKotlinNotificationManager.kt
+++ b/idea/src/org/jetbrains/jet/plugin/configuration/ConfigureKotlinNotificationManager.kt
@@ -20,6 +20,9 @@ import com.intellij.openapi.project.Project
import org.jetbrains.jet.plugin.configuration.ui.notifications.ConfigureKotlinNotification
import com.intellij.notification.NotificationsManager
import com.intellij.notification.Notification
+import com.intellij.openapi.projectRoots.Sdk
+import org.jetbrains.jet.plugin.configuration.ui.notifications.AbsentSdkAnnotationsNotification
+import org.jetbrains.jet.plugin.configuration.ui.notifications.*
object ConfigureKotlinNotificationManager: KotlinSingleNotificationManager {
fun notify(project: Project) {
@@ -27,6 +30,12 @@ object ConfigureKotlinNotificationManager: KotlinSingleNotificationManager {
+ fun notify(project: Project, sdks: Collection) {
+ notify(project, AbsentSdkAnnotationsNotification(sdks, getNotificationTitle(sdks), getNotificationString(sdks)))
+ }
+}
+
trait KotlinSingleNotificationManager {
fun notify(project: Project, notification: T) {
val notificationsManager = NotificationsManager.getNotificationsManager()
diff --git a/idea/src/org/jetbrains/jet/plugin/configuration/ui/AbsentJdkAnnotationsNotifications.java b/idea/src/org/jetbrains/jet/plugin/configuration/ui/AbsentJdkAnnotationsNotifications.java
index 4ef6dfa1fa2..8ebc9b5668d 100644
--- a/idea/src/org/jetbrains/jet/plugin/configuration/ui/AbsentJdkAnnotationsNotifications.java
+++ b/idea/src/org/jetbrains/jet/plugin/configuration/ui/AbsentJdkAnnotationsNotifications.java
@@ -16,83 +16,111 @@
package org.jetbrains.jet.plugin.configuration.ui;
-import com.intellij.openapi.compiler.CompilerManager;
-import com.intellij.openapi.fileEditor.FileEditor;
+import com.google.common.collect.Sets;
+import com.intellij.ProjectTopics;
+import com.intellij.openapi.application.ApplicationManager;
+import com.intellij.openapi.components.AbstractProjectComponent;
import com.intellij.openapi.module.Module;
-import com.intellij.openapi.module.ModuleUtil;
+import com.intellij.openapi.project.DumbService;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.projectRoots.Sdk;
+import com.intellij.openapi.roots.ModuleRootAdapter;
+import com.intellij.openapi.roots.ModuleRootEvent;
import com.intellij.openapi.roots.ModuleRootManager;
-import com.intellij.openapi.util.Key;
-import com.intellij.openapi.vfs.VirtualFile;
-import com.intellij.psi.JavaPsiFacade;
-import com.intellij.psi.search.GlobalSearchScope;
-import com.intellij.ui.EditorNotificationPanel;
-import com.intellij.ui.EditorNotifications;
+import com.intellij.openapi.vfs.*;
+import com.intellij.openapi.vfs.impl.BulkVirtualFileListenerAdapter;
+import com.intellij.util.messages.MessageBusConnection;
import org.jetbrains.annotations.NotNull;
-import org.jetbrains.annotations.Nullable;
-import org.jetbrains.jet.plugin.JetFileType;
+import org.jetbrains.jet.plugin.configuration.AbsentSdkAnnotationsNotificationManager;
+import org.jetbrains.jet.plugin.configuration.ConfigureKotlinInProjectUtils;
+import org.jetbrains.jet.plugin.configuration.ui.notifications.NotificationsPackage;
+import org.jetbrains.jet.plugin.framework.KotlinFrameworkDetector;
import org.jetbrains.jet.plugin.versions.KotlinRuntimeLibraryUtil;
-public class AbsentJdkAnnotationsNotifications extends EditorNotifications.Provider {
- private static final Key KEY = Key.create("add.kotlin.jdk.annotations");
+import java.util.Collection;
+import java.util.Set;
- private final Project project;
+public class AbsentJdkAnnotationsNotifications extends AbstractProjectComponent {
- public AbsentJdkAnnotationsNotifications(Project project) {
- this.project = project;
+ protected AbsentJdkAnnotationsNotifications(Project project) {
+ super(project);
}
@Override
- @Nullable
- public EditorNotificationPanel createNotificationPanel(VirtualFile file, FileEditor fileEditor) {
- if (file.getFileType() != JetFileType.INSTANCE) return null;
- if (CompilerManager.getInstance(project).isExcludedFromCompilation(file)) return null;
-
- final Module module = ModuleUtil.findModuleForFile(file, project);
- if (module == null) return null;
-
- GlobalSearchScope scope = module.getModuleWithDependenciesAndLibrariesScope(false);
- if (JavaPsiFacade.getInstance(project).findClass("jet.JetObject", scope) == null) return null;
-
- Sdk sdk = ModuleRootManager.getInstance(module).getSdk();
- if (sdk == null) return null;
-
- return createMissingSdkAnnotationsPanelIfNeeded(module, sdk);
- }
-
- private EditorNotificationPanel createMissingSdkAnnotationsPanelIfNeeded(final Module module, @NotNull Sdk sdk) {
- final boolean isAndroidSdk = isAndroidSdk(sdk);
- if (KotlinRuntimeLibraryUtil.jdkAnnotationsArePresent(module)) {
- if (!isAndroidSdk || KotlinRuntimeLibraryUtil.androidSdkAnnotationsArePresent(module)) {
- return null;
- }
- }
-
- EditorNotificationPanel panel = new EditorNotificationPanel();
- String sdkKind = isAndroidSdk ? "Android SDK" : "JDK";
- panel.setText("Kotlin external annotations for " + sdkKind + " are not set for '" + sdk.getName() + "'.");
- panel.createActionLabel("Set up Kotlin " + sdkKind + " annotations", new Runnable() {
+ public void projectOpened() {
+ super.projectOpened();
+ MessageBusConnection connection = myProject.getMessageBus().connect();
+ connection.subscribe(DumbService.DUMB_MODE, new DumbService.DumbModeListener() {
@Override
- public void run() {
- if (!KotlinRuntimeLibraryUtil.jdkAnnotationsArePresent(module)) {
- KotlinRuntimeLibraryUtil.addJdkAnnotations(module);
- }
- if (isAndroidSdk && !KotlinRuntimeLibraryUtil.androidSdkAnnotationsArePresent(module)) {
- KotlinRuntimeLibraryUtil.addAndroidSdkAnnotations(module);
- }
+ public void enteredDumbMode() {
+ }
+
+ @Override
+ public void exitDumbMode() {
+ showNotificationIfNeeded();
+ }
+ });
+ connection.subscribe(ProjectTopics.PROJECT_ROOTS, new ModuleRootAdapter() {
+ @Override
+ public void rootsChanged(ModuleRootEvent event) {
+ showNotificationIfNeeded();
}
});
- return panel;
+ connection.subscribe(VirtualFileManager.VFS_CHANGES, new BulkVirtualFileListenerAdapter(new VirtualFileAdapter() {
+ @Override
+ public void fileDeleted(VirtualFileEvent event) {
+ showNotificationIfNeeded();
+ }
+
+ @Override
+ public void fileCreated(VirtualFileEvent event) {
+ showNotificationIfNeeded();
+ }
+
+ @Override
+ public void fileMoved(VirtualFileMoveEvent event) {
+ showNotificationIfNeeded();
+ }
+
+ @Override
+ public void fileCopied(VirtualFileCopyEvent event) {
+ showNotificationIfNeeded();
+ }
+ }));
}
- @Override
- public Key getKey() {
- return KEY;
+ public void showNotificationIfNeeded() {
+ ApplicationManager.getApplication().invokeLater(new Runnable() {
+ @Override
+ public void run() {
+ Collection sdks = collectSdksWithoutAnnotations();
+ if (sdks.isEmpty()) return;
+ AbsentSdkAnnotationsNotificationManager.instance$.notify(myProject, sdks);
+ }
+ });
}
- private static boolean isAndroidSdk(@NotNull Sdk sdk) {
- return sdk.getSdkType().getName().equals("Android SDK");
+ @NotNull
+ private Collection collectSdksWithoutAnnotations() {
+ Set sdks = Sets.newHashSet();
+ for (Module module : ConfigureKotlinInProjectUtils.getModulesWithKotlinFiles(myProject)) {
+ if (KotlinFrameworkDetector.isJavaKotlinModule(module)) {
+ Sdk sdk = ModuleRootManager.getInstance(module).getSdk();
+ if (sdk != null && !isAnnotationsArePresent(sdk)) {
+ sdks.add(sdk);
+ }
+ }
+ }
+ return sdks;
}
-}
+
+ private static boolean isAnnotationsArePresent(@NotNull Sdk sdk) {
+ if (!KotlinRuntimeLibraryUtil.jdkAnnotationsArePresent(sdk)) {
+ return false;
+ }
+
+ boolean isAndroidSdk = NotificationsPackage.isAndroidSdk(sdk);
+ return !(isAndroidSdk && !KotlinRuntimeLibraryUtil.androidSdkAnnotationsArePresent(sdk));
+ }
+}
\ No newline at end of file
diff --git a/idea/src/org/jetbrains/jet/plugin/configuration/ui/notifications/AbsentSdkAnnotationsNotification.kt b/idea/src/org/jetbrains/jet/plugin/configuration/ui/notifications/AbsentSdkAnnotationsNotification.kt
new file mode 100644
index 00000000000..6f1faff5721
--- /dev/null
+++ b/idea/src/org/jetbrains/jet/plugin/configuration/ui/notifications/AbsentSdkAnnotationsNotification.kt
@@ -0,0 +1,69 @@
+/*
+ * 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.ui.notifications
+
+import com.intellij.notification.Notification
+import com.intellij.notification.NotificationListener
+import com.intellij.notification.NotificationType
+import com.intellij.openapi.projectRoots.Sdk
+import org.jetbrains.jet.plugin.versions.KotlinRuntimeLibraryUtil
+import javax.swing.event.HyperlinkEvent
+
+private val GROUP_ID = "Configure Kotlin: absent sdk annotations"
+
+public class AbsentSdkAnnotationsNotification(sdks: Collection, val title: String, val text: String) :
+ Notification(
+ GROUP_ID,
+ title, text,
+ NotificationType.WARNING,
+ AbsentAnnotationsListener(sdks) // Workaround for KT-4086
+ )
+{
+ override fun equals(obj: Any?) = obj is AbsentSdkAnnotationsNotification && text == obj.text && title == obj.title
+
+ override fun hashCode(): Int = 31 * title.hashCode() + text.hashCode()
+}
+
+fun isAndroidSdk(sdk: Sdk) = sdk.getSdkType().getName() == "Android SDK"
+
+fun getNotificationTitle(sdks: Collection) = "Kotlin external annotations for ${getSdkKind(sdks)} are not set for " + getSdkName(sdks)
+
+fun getNotificationString(sdks: Collection) = "Set up Kotlin ${getSdkKind(sdks)} annotations"
+
+private class AbsentAnnotationsListener(val sdks: Collection): NotificationListener {
+ override fun hyperlinkUpdate(notification: Notification, event: HyperlinkEvent) {
+ if (event.getEventType() == HyperlinkEvent.EventType.ACTIVATED) {
+ if (event.getDescription() == "configure") {
+ for (sdk in sdks) {
+ if (!KotlinRuntimeLibraryUtil.jdkAnnotationsArePresent(sdk)) {
+ KotlinRuntimeLibraryUtil.addJdkAnnotations(sdk);
+ }
+ if (isAndroidSdk(sdk) && !KotlinRuntimeLibraryUtil.androidSdkAnnotationsArePresent(sdk)) {
+ KotlinRuntimeLibraryUtil.addAndroidSdkAnnotations(sdk);
+ }
+ }
+ }
+ notification.expire()
+ }
+ }
+}
+
+private fun getSdkKind(sdks: Collection) = if (sdks.size() > 1) "SDK" else if (isAndroidSdk(sdks.first())) "Android SDK" else "JDK"
+
+private fun getSdkName(sdks: Collection) = if (sdks.size() > 1) "several SDKs" else "'${sdks.first().getName()}'"
+
+
diff --git a/idea/src/org/jetbrains/jet/plugin/versions/KotlinRuntimeLibraryUtil.java b/idea/src/org/jetbrains/jet/plugin/versions/KotlinRuntimeLibraryUtil.java
index b2ed9acf0e6..d571f35deb3 100644
--- a/idea/src/org/jetbrains/jet/plugin/versions/KotlinRuntimeLibraryUtil.java
+++ b/idea/src/org/jetbrains/jet/plugin/versions/KotlinRuntimeLibraryUtil.java
@@ -56,7 +56,6 @@ import org.jetbrains.jet.utils.PathUtil;
import java.io.File;
import java.io.IOException;
import java.util.Collection;
-import java.util.List;
import java.util.Set;
import static com.intellij.util.PathUtil.getLocalFile;
@@ -95,19 +94,15 @@ public class KotlinRuntimeLibraryUtil {
return badRoots;
}
- public static void addJdkAnnotations(@NotNull Module module) {
- addAnnotations(module, PathUtil.getKotlinPathsForIdeaPlugin().getJdkAnnotationsPath());
+ public static void addJdkAnnotations(@NotNull Sdk sdk) {
+ addAnnotations(sdk, PathUtil.getKotlinPathsForIdeaPlugin().getJdkAnnotationsPath());
}
- public static void addAndroidSdkAnnotations(@NotNull Module module) {
- addAnnotations(module, PathUtil.getKotlinPathsForIdeaPlugin().getAndroidSdkAnnotationsPath());
+ public static void addAndroidSdkAnnotations(@NotNull Sdk sdk) {
+ addAnnotations(sdk, PathUtil.getKotlinPathsForIdeaPlugin().getAndroidSdkAnnotationsPath());
}
- private static void addAnnotations(@NotNull Module module, @NotNull File annotationsPath) {
- Sdk sdk = ModuleRootManager.getInstance(module).getSdk();
- if (sdk == null) {
- return;
- }
+ private static void addAnnotations(@NotNull Sdk sdk, @NotNull File annotationsPath) {
if (annotationsPath.exists()) {
VirtualFile jdkAnnotationsJar = LocalFileSystem.getInstance().findFileByIoFile(annotationsPath);
if (jdkAnnotationsJar != null) {
@@ -119,17 +114,15 @@ public class KotlinRuntimeLibraryUtil {
}
}
- public static boolean jdkAnnotationsArePresent(@NotNull Module module) {
- return areAnnotationsPresent(module, PathUtil.JDK_ANNOTATIONS_JAR);
+ public static boolean jdkAnnotationsArePresent(@NotNull Sdk sdk) {
+ return areAnnotationsPresent(sdk, PathUtil.JDK_ANNOTATIONS_JAR);
}
- public static boolean androidSdkAnnotationsArePresent(@NotNull Module module) {
- return areAnnotationsPresent(module, PathUtil.ANDROID_SDK_ANNOTATIONS_JAR);
+ public static boolean androidSdkAnnotationsArePresent(@NotNull Sdk sdk) {
+ return areAnnotationsPresent(sdk, PathUtil.ANDROID_SDK_ANNOTATIONS_JAR);
}
- private static boolean areAnnotationsPresent(@NotNull Module module, @NotNull final String jarFileName) {
- Sdk sdk = ModuleRootManager.getInstance(module).getSdk();
- if (sdk == null) return false;
+ private static boolean areAnnotationsPresent(@NotNull Sdk sdk, @NotNull final String jarFileName) {
return ContainerUtil.exists(sdk.getRootProvider().getFiles(AnnotationOrderRootType.getInstance()),
new Condition() {
@Override