Rewrite Absent Jdk Annotations stripe to balloon

This commit is contained in:
Natalia Ukhorskaya
2013-10-16 16:20:35 +04:00
parent d8736e9442
commit 6c503e5778
5 changed files with 178 additions and 77 deletions
+3 -1
View File
@@ -34,6 +34,9 @@
<component>
<implementation-class>org.jetbrains.jet.plugin.ktSignature.KotlinSignatureInJavaMarkerUpdater</implementation-class>
</component>
<component>
<implementation-class>org.jetbrains.jet.plugin.configuration.ui.AbsentJdkAnnotationsNotifications</implementation-class>
</component>
</project-components>
<application-components>
@@ -310,7 +313,6 @@
<contentBasedClassFileProcessor implementation="org.jetbrains.jet.plugin.libraries.JetContentBasedFileSubstitutor"/>
<editorNotificationProvider implementation="org.jetbrains.jet.plugin.configuration.ui.AbsentJdkAnnotationsNotifications"/>
<editorNotificationProvider implementation="org.jetbrains.jet.plugin.quickfix.IncorrectSourceRootNameNotification"/>
<editorNotificationProvider implementation="org.jetbrains.jet.plugin.versions.UnsupportedAbiVersionNotificationPanelProvider"/>
@@ -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<ConfigureKotlinNotification> {
fun notify(project: Project) {
@@ -27,6 +30,12 @@ object ConfigureKotlinNotificationManager: KotlinSingleNotificationManager<Confi
}
}
object AbsentSdkAnnotationsNotificationManager: KotlinSingleNotificationManager<AbsentSdkAnnotationsNotification> {
fun notify(project: Project, sdks: Collection<Sdk>) {
notify(project, AbsentSdkAnnotationsNotification(sdks, getNotificationTitle(sdks), getNotificationString(sdks)))
}
}
trait KotlinSingleNotificationManager<T: Notification> {
fun notify(project: Project, notification: T) {
val notificationsManager = NotificationsManager.getNotificationsManager()
@@ -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<EditorNotificationPanel> {
private static final Key<EditorNotificationPanel> 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<EditorNotificationPanel> getKey() {
return KEY;
public void showNotificationIfNeeded() {
ApplicationManager.getApplication().invokeLater(new Runnable() {
@Override
public void run() {
Collection<Sdk> 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<Sdk> collectSdksWithoutAnnotations() {
Set<Sdk> 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));
}
}
@@ -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<Sdk>, 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<Sdk>) = "Kotlin external annotations for ${getSdkKind(sdks)} are not set for " + getSdkName(sdks)
fun getNotificationString(sdks: Collection<Sdk>) = "<a href=\"configure\">Set up Kotlin ${getSdkKind(sdks)} annotations</a>"
private class AbsentAnnotationsListener(val sdks: Collection<Sdk>): 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<Sdk>) = if (sdks.size() > 1) "SDK" else if (isAndroidSdk(sdks.first())) "Android SDK" else "JDK"
private fun getSdkName(sdks: Collection<Sdk>) = if (sdks.size() > 1) "several SDKs" else "'${sdks.first().getName()}'"
@@ -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<VirtualFile>() {
@Override