mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-18 09:34:34 +07:00
UnknownSdkTracker - include missing SDKs that we cannot fix, add tests
GitOrigin-RevId: 0737b750ccdb1ab1e48b565d079b794763cffdbd
This commit is contained in:
committed by
intellij-monorepo-bot
parent
1e7b6a860b
commit
fcbf5a8013
+96
-71
@@ -1,28 +1,21 @@
|
||||
// Copyright 2000-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
|
||||
package com.intellij.java.codeInsight.daemon.impl;
|
||||
|
||||
import com.intellij.codeInsight.intention.IntentionActionWithOptions;
|
||||
import com.intellij.openapi.Disposable;
|
||||
import com.intellij.openapi.application.ReadAction;
|
||||
import com.intellij.openapi.application.WriteAction;
|
||||
import com.intellij.openapi.fileEditor.FileEditor;
|
||||
import com.intellij.openapi.fileEditor.ex.FileEditorManagerEx;
|
||||
import com.intellij.openapi.projectRoots.JavaSdk;
|
||||
import com.intellij.openapi.projectRoots.ProjectJdkTable;
|
||||
import com.intellij.openapi.projectRoots.Sdk;
|
||||
import com.intellij.openapi.projectRoots.impl.UnknownSdkEditorNotification;
|
||||
import com.intellij.openapi.projectRoots.impl.UnknownSdkEditorNotification.SdkFixInfo;
|
||||
import com.intellij.openapi.projectRoots.impl.UnknownSdkTracker;
|
||||
import com.intellij.openapi.roots.ModifiableRootModel;
|
||||
import com.intellij.openapi.roots.ModuleRootManager;
|
||||
import com.intellij.openapi.roots.ModuleRootModificationUtil;
|
||||
import com.intellij.openapi.roots.ProjectRootManager;
|
||||
import com.intellij.openapi.util.Disposer;
|
||||
import com.intellij.openapi.vfs.VirtualFile;
|
||||
import com.intellij.psi.PsiFile;
|
||||
import com.intellij.testFramework.IdeaTestUtil;
|
||||
import com.intellij.testFramework.fixtures.JavaCodeInsightFixtureTestCase;
|
||||
import com.intellij.ui.EditorNotificationPanel;
|
||||
import com.intellij.ui.EditorNotificationsImpl;
|
||||
import org.assertj.core.api.AssertionsForClassTypes;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.junit.Assert;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@@ -32,24 +25,92 @@ import static org.assertj.core.api.Assertions.assertThat;
|
||||
* @author Pavel.Dolgov
|
||||
*/
|
||||
public class SdkSetupNotificationTest extends JavaCodeInsightFixtureTestCase {
|
||||
|
||||
public void testProjectSdk() {
|
||||
final EditorNotificationPanel panel = configureBySdkAndText(IdeaTestUtil.getMockJdk18(), false, "Sample.java", "class Sample {}");
|
||||
assertThat(panel).isNull();
|
||||
Sdk sdk = IdeaTestUtil.getMockJdk18();
|
||||
setProjectSdk(sdk);
|
||||
ModuleRootModificationUtil.setSdkInherited(getModule());
|
||||
|
||||
final List<SdkFixInfo> fixes = detectMissingSdks();
|
||||
assertThat(fixes)
|
||||
.withFailMessage(String.valueOf(fixes))
|
||||
.isEmpty();
|
||||
}
|
||||
|
||||
public void testMissingProjectJdk() {
|
||||
WriteAction.run(() -> {
|
||||
ProjectRootManager.getInstance(getProject()).setProjectSdkName("missingSDK", JavaSdk.getInstance().getName());
|
||||
});
|
||||
|
||||
final List<SdkFixInfo> fixes = detectMissingSdks();
|
||||
assertThat(fixes)
|
||||
.withFailMessage(String.valueOf(fixes))
|
||||
.hasSize(1)
|
||||
.first()
|
||||
.returns("missingSDK", SdkFixInfo::getSdkName);
|
||||
}
|
||||
|
||||
public void testMissingModuleJdk() {
|
||||
WriteAction.run(() -> {
|
||||
ModifiableRootModel model = ModuleRootManager.getInstance(getModule()).getModifiableModel();
|
||||
model.setInvalidSdk("missingSDK", JavaSdk.getInstance().getName());
|
||||
model.commit();
|
||||
});
|
||||
|
||||
final List<SdkFixInfo> fixes = detectMissingSdks();
|
||||
assertThat(fixes)
|
||||
.withFailMessage(String.valueOf(fixes))
|
||||
.hasSize(1)
|
||||
.first()
|
||||
.returns("missingSDK", SdkFixInfo::getSdkName);
|
||||
}
|
||||
|
||||
public void testNoProjectSdk() {
|
||||
final EditorNotificationPanel panel = configureBySdkAndText(null, false, "Sample.java", "class Sample {}");
|
||||
assertSdkSetupPanelShown(panel, "Setup SDK");
|
||||
setProjectSdk(null);
|
||||
ModuleRootModificationUtil.setSdkInherited(getModule());
|
||||
|
||||
final List<SdkFixInfo> fixes = detectMissingSdks();
|
||||
assertThat(fixes)
|
||||
.withFailMessage(String.valueOf(fixes))
|
||||
.isNotEmpty();
|
||||
|
||||
/*
|
||||
final IntentionActionWithOptions action = fixes.getIntentionAction();
|
||||
assertThat(action).isNotNull();
|
||||
final String text = action.getText();
|
||||
assertThat(text).isNotNull();
|
||||
if (!text.startsWith("Setup SDK")) {
|
||||
final int length = Math.min(text.length(), "Setup SDK".length());
|
||||
assertThat(text.substring(0, length)).isEqualTo("Setup SDK");
|
||||
}*/
|
||||
}
|
||||
|
||||
public void testModuleSdk() {
|
||||
final EditorNotificationPanel panel = configureBySdkAndText(IdeaTestUtil.getMockJdk18(), true, "Sample.java", "class Sample {}");
|
||||
assertThat(panel).isNull();
|
||||
Sdk sdk = IdeaTestUtil.getMockJdk18();
|
||||
ModuleRootModificationUtil.setModuleSdk(getModule(), sdk);
|
||||
|
||||
final List<SdkFixInfo> fixes = detectMissingSdks();
|
||||
assertThat(fixes)
|
||||
.withFailMessage(String.valueOf(fixes))
|
||||
.isEmpty();
|
||||
}
|
||||
|
||||
public void testNoModuleSdk() {
|
||||
final EditorNotificationPanel panel = configureBySdkAndText(null, true, "Sample.java", "class Sample {}");
|
||||
assertSdkSetupPanelShown(panel, "Setup SDK");
|
||||
ModuleRootModificationUtil.setModuleSdk(getModule(), null);
|
||||
|
||||
final List<SdkFixInfo> fixes = detectMissingSdks();
|
||||
assertThat(fixes)
|
||||
.withFailMessage(String.valueOf(fixes))
|
||||
.isNotEmpty();
|
||||
|
||||
/*final IntentionActionWithOptions action = panel.getIntentionAction();
|
||||
assertThat(action).isNotNull();
|
||||
final String text = action.getText();
|
||||
assertThat(text).isNotNull();
|
||||
if (!text.startsWith("Setup SDK")) {
|
||||
final int length = Math.min(text.length(), "Setup SDK".length());
|
||||
assertThat(text.substring(0, length)).isEqualTo("Setup SDK");
|
||||
}*/
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -59,61 +120,25 @@ public class SdkSetupNotificationTest extends JavaCodeInsightFixtureTestCase {
|
||||
setProjectSdk(IdeaTestUtil.getMockJdk17());
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private List<SdkFixInfo> detectMissingSdks() {
|
||||
UnknownSdkTracker.getInstance(getProject()).updateUnknownSdks();
|
||||
|
||||
@Nullable
|
||||
@SuppressWarnings("SameParameterValue")
|
||||
protected EditorNotificationPanel configureBySdkAndText(@Nullable Sdk sdk,
|
||||
boolean isModuleSdk,
|
||||
@NotNull String name,
|
||||
@NotNull String text) {
|
||||
if (isModuleSdk) {
|
||||
ModuleRootModificationUtil.setModuleSdk(getModule(), sdk);
|
||||
}
|
||||
else {
|
||||
setProjectSdk(sdk);
|
||||
ModuleRootModificationUtil.setSdkInherited(getModule());
|
||||
}
|
||||
|
||||
final PsiFile psiFile = myFixture.configureByText(name, text);
|
||||
FileEditorManagerEx fileEditorManager = FileEditorManagerEx.getInstanceEx(getProject());
|
||||
VirtualFile virtualFile = psiFile.getVirtualFile();
|
||||
final FileEditor[] editors = fileEditorManager.openFile(virtualFile, true);
|
||||
Disposer.register(myFixture.getTestRootDisposable(), new Disposable() {
|
||||
@Override
|
||||
public void dispose() {
|
||||
fileEditorManager.closeFile(virtualFile);
|
||||
}
|
||||
});
|
||||
AssertionsForClassTypes.assertThat(editors).hasSize(1);
|
||||
EditorNotificationsImpl.completeAsyncTasks();
|
||||
|
||||
List<? extends EditorNotificationPanel> data = editors[0].getUserData(UnknownSdkEditorNotification.NOTIFICATIONS);
|
||||
if (data == null) return null;
|
||||
Assert.assertEquals("Only one notification was expected, but were " + data, 1, data.size());
|
||||
return data.iterator().next();
|
||||
return UnknownSdkEditorNotification.getInstance(getProject()).getNotifications();
|
||||
}
|
||||
|
||||
private void setProjectSdk(@Nullable Sdk sdk) {
|
||||
if (sdk != null) {
|
||||
final Sdk foundJdk = ReadAction.compute(() -> ProjectJdkTable.getInstance().findJdk(sdk.getName()));
|
||||
if (foundJdk == null) {
|
||||
WriteAction.run(() -> ProjectJdkTable.getInstance().addJdk(sdk, myFixture.getProjectDisposable()));
|
||||
}
|
||||
}
|
||||
WriteAction.run(() -> ProjectRootManager.getInstance(getProject()).setProjectSdk(sdk));
|
||||
}
|
||||
WriteAction.run(() -> {
|
||||
ProjectJdkTable jdkTable = ProjectJdkTable.getInstance();
|
||||
|
||||
@SuppressWarnings("SameParameterValue")
|
||||
private static void assertSdkSetupPanelShown(@Nullable EditorNotificationPanel panel,
|
||||
@NotNull String expectedMessagePrefix) {
|
||||
AssertionsForClassTypes.assertThat(panel).isNotNull();
|
||||
final IntentionActionWithOptions action = panel.getIntentionAction();
|
||||
AssertionsForClassTypes.assertThat(action).isNotNull();
|
||||
final String text = action.getText();
|
||||
AssertionsForClassTypes.assertThat(text).isNotNull();
|
||||
if (!text.startsWith(expectedMessagePrefix)) {
|
||||
final int length = Math.min(text.length(), expectedMessagePrefix.length());
|
||||
AssertionsForClassTypes.assertThat(text.substring(0, length)).isEqualTo(expectedMessagePrefix);
|
||||
}
|
||||
if (sdk != null) {
|
||||
final Sdk foundJdk = jdkTable.findJdk(sdk.getName());
|
||||
if (foundJdk == null) {
|
||||
jdkTable.addJdk(sdk, myFixture.getProjectDisposable());
|
||||
}
|
||||
}
|
||||
|
||||
ProjectRootManager.getInstance(getProject()).setProjectSdk(sdk);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
+2
-1
@@ -33,10 +33,11 @@ public class UnknownSdkBalloonNotification {
|
||||
}
|
||||
|
||||
public void notifyFixedSdks(@NotNull Map<? extends UnknownSdk, LocalSdkFix> localFixes) {
|
||||
if (localFixes.isEmpty()) return;
|
||||
|
||||
final String title;
|
||||
final String change;
|
||||
final StringBuilder message = new StringBuilder();
|
||||
if (localFixes.isEmpty()) return;
|
||||
|
||||
Set<String> usages = new TreeSet<>();
|
||||
for (Map.Entry<? extends UnknownSdk, LocalSdkFix> entry : localFixes.entrySet()) {
|
||||
|
||||
+124
-34
@@ -1,24 +1,27 @@
|
||||
// Copyright 2000-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
|
||||
package com.intellij.openapi.projectRoots.impl;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.intellij.openapi.Disposable;
|
||||
import com.intellij.openapi.fileEditor.FileEditor;
|
||||
import com.intellij.openapi.fileEditor.FileEditorManager;
|
||||
import com.intellij.openapi.fileEditor.FileEditorManagerListener;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.openapi.projectRoots.SdkType;
|
||||
import com.intellij.openapi.projectRoots.impl.UnknownSdkResolver.DownloadSdkFix;
|
||||
import com.intellij.openapi.projectRoots.impl.UnknownSdkResolver.UnknownSdk;
|
||||
import com.intellij.openapi.util.Key;
|
||||
import com.intellij.openapi.util.Pair;
|
||||
import com.intellij.openapi.vfs.VirtualFile;
|
||||
import com.intellij.ui.EditorNotificationPanel;
|
||||
import com.intellij.util.SmartList;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import javax.swing.*;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.awt.*;
|
||||
import java.util.*;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public class UnknownSdkEditorNotification implements Disposable {
|
||||
public static final Key<List<MissingSdkNotificationPanel>> NOTIFICATIONS = Key.create("notifications added to the editor");
|
||||
@@ -31,7 +34,7 @@ public class UnknownSdkEditorNotification implements Disposable {
|
||||
|
||||
private final Project myProject;
|
||||
private final FileEditorManager myFileEditorManager;
|
||||
private Map<UnknownSdk, DownloadSdkFix> myNotifications = new HashMap<>();
|
||||
private final Map<String, SdkFixInfo> myNotifications = new TreeMap<>();
|
||||
|
||||
UnknownSdkEditorNotification(@NotNull Project project) {
|
||||
myProject = project;
|
||||
@@ -48,36 +51,40 @@ public class UnknownSdkEditorNotification implements Disposable {
|
||||
});
|
||||
}
|
||||
|
||||
private static class MissingSdkNotificationPanel extends EditorNotificationPanel {
|
||||
private final UnknownSdk myInfo;
|
||||
|
||||
private MissingSdkNotificationPanel(@NotNull final UnknownSdk info) {
|
||||
myInfo = info;
|
||||
}
|
||||
|
||||
public boolean isSameProblemAs(@NotNull MissingSdkNotificationPanel panel) {
|
||||
return this.myInfo.equals(panel.myInfo);
|
||||
}
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private MissingSdkNotificationPanel createPanelFor(@NotNull UnknownSdk info,
|
||||
@NotNull DownloadSdkFix fix) {
|
||||
String sdkName = info.getSdkType().getPresentableName();
|
||||
private MissingSdkNotificationPanel createPanelFor(@NotNull SdkFixInfo info) {
|
||||
Pair<UnknownSdk, DownloadSdkFix> fixInfo = info.getFixInfo();
|
||||
String sdkName = info.getSdkName();
|
||||
|
||||
SdkType sdkType = fixInfo != null ? fixInfo.first.getSdkType() : null;
|
||||
String sdkTypeName = sdkType != null ? sdkType.getPresentableName() : "SDK";
|
||||
|
||||
MissingSdkNotificationPanel panel = new MissingSdkNotificationPanel(info);
|
||||
panel.setProject(myProject);
|
||||
panel.setProviderKey(EDITOR_NOTIFICATIONS_KEY);
|
||||
panel.setText(sdkName + " \"" + info.getSdkName() + "\" is missing");
|
||||
panel.setText(sdkTypeName + " \"" + sdkName + "\" is missing");
|
||||
|
||||
panel.createActionLabel("Download " + sdkName + " (" + fix.getDownloadDescription() + ")", () -> {
|
||||
removeNotification(panel);
|
||||
UnknownSdkTracker.getInstance(myProject).applyDownloadableFix(info, fix);
|
||||
});
|
||||
if (fixInfo != null) {
|
||||
UnknownSdk unknownSdk = fixInfo.first;
|
||||
DownloadSdkFix fix = fixInfo.second;
|
||||
|
||||
panel.createActionLabel("Configure...", () -> {
|
||||
UnknownSdkTracker.getInstance(myProject).showSdkSelectionPopup(info, panel, () -> removeNotification(panel));
|
||||
});
|
||||
panel.createActionLabel("Download " + sdkTypeName + " (" + fix.getDownloadDescription() + ")", () -> {
|
||||
removeNotification(panel);
|
||||
UnknownSdkTracker.getInstance(myProject).applyDownloadableFix(unknownSdk, fix);
|
||||
});
|
||||
}
|
||||
|
||||
panel.createActionLabel("Configure...",
|
||||
() -> {
|
||||
//FileEditorManager#addTopComponent wraps the panel to implement borders, unwrapping
|
||||
Container container = panel.getParent();
|
||||
if (container == null) container = panel;
|
||||
|
||||
UnknownSdkTracker
|
||||
.getInstance(myProject)
|
||||
.showSdkSelectionPopup(sdkName, sdkType, container, () -> removeNotification(panel));
|
||||
}
|
||||
);
|
||||
|
||||
return panel;
|
||||
}
|
||||
@@ -85,8 +92,22 @@ public class UnknownSdkEditorNotification implements Disposable {
|
||||
@Override
|
||||
public void dispose() { }
|
||||
|
||||
public void showNotifications(@NotNull final Map<? extends UnknownSdk, DownloadSdkFix> files) {
|
||||
myNotifications = new HashMap<>(files);
|
||||
@NotNull
|
||||
public List<SdkFixInfo> getNotifications() {
|
||||
return ImmutableList.copyOf(myNotifications.values());
|
||||
}
|
||||
|
||||
public void showNotifications(@NotNull List<String> unifiableSdkNames,
|
||||
@NotNull Map<UnknownSdk, DownloadSdkFix> files) {
|
||||
myNotifications.clear();
|
||||
|
||||
for (String name : unifiableSdkNames) {
|
||||
myNotifications.put(name, SdkFixInfo.forBroken(name));
|
||||
}
|
||||
|
||||
for (Map.Entry<UnknownSdk, DownloadSdkFix> e : files.entrySet()) {
|
||||
myNotifications.put(e.getKey().getSdkName(), SdkFixInfo.forFix(e));
|
||||
}
|
||||
|
||||
for (FileEditor editor : myFileEditorManager.getAllEditors()) {
|
||||
updateEditorNotifications(editor);
|
||||
@@ -94,12 +115,14 @@ public class UnknownSdkEditorNotification implements Disposable {
|
||||
}
|
||||
|
||||
private void removeNotification(@NotNull MissingSdkNotificationPanel expiredPanel) {
|
||||
myNotifications.remove(expiredPanel.myInfo);
|
||||
myNotifications.remove(expiredPanel.myInfo.getSdkName());
|
||||
|
||||
for (FileEditor editor : myFileEditorManager.getAllEditors()) {
|
||||
List<MissingSdkNotificationPanel> notifications = editor.getUserData(NOTIFICATIONS);
|
||||
if (notifications == null) continue;
|
||||
|
||||
for (MissingSdkNotificationPanel panel : new ArrayList<>(notifications)) {
|
||||
if (panel.isSameProblemAs(expiredPanel)) {
|
||||
if (panel.myInfo.equals(expiredPanel.myInfo)) {
|
||||
myFileEditorManager.removeTopComponent(editor, panel);
|
||||
notifications.remove(panel);
|
||||
}
|
||||
@@ -116,16 +139,83 @@ public class UnknownSdkEditorNotification implements Disposable {
|
||||
myFileEditorManager.removeTopComponent(editor, component);
|
||||
}
|
||||
notifications.clear();
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
notifications = new SmartList<>();
|
||||
editor.putUserData(NOTIFICATIONS, notifications);
|
||||
}
|
||||
|
||||
for (Map.Entry<UnknownSdk, DownloadSdkFix> e : myNotifications.entrySet()) {
|
||||
MissingSdkNotificationPanel notification = createPanelFor(e.getKey(), e.getValue());
|
||||
for (SdkFixInfo info : myNotifications.values()) {
|
||||
MissingSdkNotificationPanel notification = createPanelFor(info);
|
||||
|
||||
notifications.add(notification);
|
||||
myFileEditorManager.addTopComponent(editor, notification);
|
||||
}
|
||||
}
|
||||
|
||||
public static class MissingSdkNotificationPanel extends EditorNotificationPanel {
|
||||
private final SdkFixInfo myInfo;
|
||||
|
||||
private MissingSdkNotificationPanel(@NotNull final SdkFixInfo info) {
|
||||
myInfo = info;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public SdkFixInfo getInfo() {
|
||||
return myInfo;
|
||||
}
|
||||
}
|
||||
|
||||
public static class SdkFixInfo {
|
||||
@NotNull
|
||||
static SdkFixInfo forBroken(@NotNull String sdkName) {
|
||||
return new SdkFixInfo(sdkName);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
static SdkFixInfo forFix(@NotNull Map.Entry<? extends UnknownSdk, DownloadSdkFix> e) {
|
||||
UnknownSdk sdk = e.getKey();
|
||||
DownloadSdkFix fix = e.getValue();
|
||||
return new SdkFixInfo(sdk.getSdkName()) {
|
||||
@NotNull
|
||||
@Override
|
||||
public Pair<UnknownSdk, DownloadSdkFix> getFixInfo() {
|
||||
return Pair.create(sdk, fix);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private final String mySdkName;
|
||||
|
||||
private SdkFixInfo(@NotNull String sdkName) {
|
||||
mySdkName = sdkName;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public String getSdkName() {
|
||||
return mySdkName;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Pair<UnknownSdk, DownloadSdkFix> getFixInfo() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final int hashCode() {
|
||||
return Objects.hashCode(mySdkName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public final boolean equals(Object obj) {
|
||||
return obj instanceof SdkFixInfo && Objects.equals(((SdkFixInfo)obj).mySdkName, mySdkName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "SdkFixInfo{" +
|
||||
"mySdkName='" + mySdkName + '\'' +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+85
-91
@@ -1,7 +1,9 @@
|
||||
// Copyright 2000-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
|
||||
package com.intellij.openapi.projectRoots.impl;
|
||||
|
||||
import com.google.common.collect.*;
|
||||
import com.intellij.openapi.Disposable;
|
||||
import com.intellij.openapi.application.Application;
|
||||
import com.intellij.openapi.application.ApplicationManager;
|
||||
import com.intellij.openapi.application.ReadAction;
|
||||
import com.intellij.openapi.application.WriteAction;
|
||||
@@ -25,7 +27,9 @@ import com.intellij.openapi.roots.ui.configuration.projectRoot.SdkDownloadTracke
|
||||
import com.intellij.openapi.ui.Messages;
|
||||
import com.intellij.openapi.util.Disposer;
|
||||
import com.intellij.openapi.util.io.FileUtil;
|
||||
import com.intellij.util.TripleFunction;
|
||||
import com.intellij.util.containers.ContainerUtil;
|
||||
import com.intellij.util.ui.UIUtil;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
@@ -59,61 +63,77 @@ public class UnknownSdkTracker {
|
||||
.run(new Task.Backgroundable(myProject, "Resolving SDKs", false, ALWAYS_BACKGROUND) {
|
||||
@Override
|
||||
public void run(@NotNull ProgressIndicator indicator) {
|
||||
List<UnknownSdkLookup> lookups = new ArrayList<>();
|
||||
UnknownSdkResolver.EP_NAME.forEachExtensionSafe(ext -> {
|
||||
UnknownSdkLookup resolver = ext.createResolver(UnknownSdkTracker.this.myProject, indicator);
|
||||
if (resolver != null) {
|
||||
lookups.add(resolver);
|
||||
}
|
||||
});
|
||||
|
||||
if (lookups.isEmpty()) return;
|
||||
updateUnknownSdksWithProgress(indicator, lookups);
|
||||
updateUnknownSdksWithProgress(indicator);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void updateUnknownSdksWithProgress(@NotNull ProgressIndicator indicator,
|
||||
@NotNull List<UnknownSdkLookup> lookups) {
|
||||
List<MissingSdkInfo> fixable = ReadAction.compute(() -> collectUnknownSdks());
|
||||
if (fixable.isEmpty()) return;
|
||||
private void updateUnknownSdksWithProgress(@NotNull ProgressIndicator indicator) {
|
||||
List<String> totallyUnknownSdks = new ArrayList<>();
|
||||
List<UnknownSdk> fixable = new ArrayList<>();
|
||||
ReadAction.run(() -> collectAndGroupSdkUsages(totallyUnknownSdks, fixable));
|
||||
|
||||
Map<MissingSdkInfo, LocalSdkFix> localFixes = findLocalFixes(indicator, fixable, lookups);
|
||||
Map<MissingSdkInfo, DownloadSdkFix> downloadFixes = findDownloadFixes(indicator,
|
||||
ContainerUtil.filter(fixable, info -> !localFixes.containsKey(info)),
|
||||
lookups);
|
||||
|
||||
if (localFixes.isEmpty() && downloadFixes.isEmpty()) return;
|
||||
List<UnknownSdkLookup> lookups = collectSdkLookups(indicator);
|
||||
Map<UnknownSdk, LocalSdkFix> localFixes = findFixesAndRemoveFixable(indicator, fixable, lookups, UnknownSdkLookup::proposeLocalFix);
|
||||
Map<UnknownSdk, DownloadSdkFix> downloadFixes = findFixesAndRemoveFixable(indicator, fixable, lookups, UnknownSdkLookup::proposeDownload);
|
||||
fixable.forEach(it -> totallyUnknownSdks.add(it.getSdkName()));
|
||||
|
||||
ApplicationManager.getApplication().invokeLater(() -> {
|
||||
indicator.setText("Configuring SDKs...");
|
||||
configureLocalSdks(localFixes);
|
||||
|
||||
UnknownSdkBalloonNotification.getInstance(myProject).notifyFixedSdks(localFixes);
|
||||
UnknownSdkEditorNotification.getInstance(myProject).showNotifications(downloadFixes);
|
||||
UnknownSdkEditorNotification.getInstance(myProject).showNotifications(totallyUnknownSdks, downloadFixes);
|
||||
});
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private List<MissingSdkInfo> collectUnknownSdks() {
|
||||
Map<String, MissingSdkInfo> myInfos = new HashMap<>();
|
||||
List<SdkUsage> usages = SdkUsagesCollector.getInstance(myProject).collectSdkUsages();
|
||||
private List<UnknownSdkLookup> collectSdkLookups(@NotNull ProgressIndicator indicator) {
|
||||
List<UnknownSdkLookup> lookups = new ArrayList<>();
|
||||
|
||||
UnknownSdkResolver.EP_NAME.forEachExtensionSafe(ext -> {
|
||||
UnknownSdkLookup resolver = ext.createResolver(myProject, indicator);
|
||||
if (resolver != null) {
|
||||
lookups.add(resolver);
|
||||
}
|
||||
});
|
||||
return lookups;
|
||||
}
|
||||
|
||||
private void collectAndGroupSdkUsages(@NotNull List<String> totallyUnknownSdks,
|
||||
@NotNull List<UnknownSdk> resolvableSdks) {
|
||||
SetMultimap<String, String> sdkToTypes = MultimapBuilder
|
||||
.treeKeys(String.CASE_INSENSITIVE_ORDER)
|
||||
.hashSetValues()
|
||||
.build();
|
||||
|
||||
ProjectJdkTable jdkTable = ProjectJdkTable.getInstance();
|
||||
for (SdkUsage usage : usages) {
|
||||
for (SdkUsage usage : SdkUsagesCollector.getInstance(myProject).collectSdkUsages()) {
|
||||
String sdkName = usage.getSdkName();
|
||||
|
||||
//we do not track existing SDKs
|
||||
if (jdkTable.findJdk(sdkName) != null) continue;
|
||||
|
||||
MissingSdkInfo info = myInfos.get(sdkName);
|
||||
if (info == null) {
|
||||
info = new MissingSdkInfo(sdkName);
|
||||
myInfos.put(sdkName, info);
|
||||
}
|
||||
|
||||
info.attachSdkType(usage.getSdkTypeName());
|
||||
String typeName = usage.getSdkTypeName();
|
||||
sdkToTypes.put(sdkName, typeName);
|
||||
}
|
||||
|
||||
return ContainerUtil.filter(myInfos.values(), sdk -> sdk.mySdkType != null);
|
||||
for (Map.Entry<String, Collection<String>> entry : sdkToTypes.asMap().entrySet()) {
|
||||
Collection<String> sdkTypes = entry.getValue();
|
||||
|
||||
SdkType sdkType = null;
|
||||
if (sdkTypes.size() == 1) {
|
||||
sdkType = SdkType.findByName(ContainerUtil.getFirstItem(sdkTypes));
|
||||
}
|
||||
|
||||
String sdkName = entry.getKey();
|
||||
if (sdkType == null) {
|
||||
totallyUnknownSdks.add(sdkName);
|
||||
}
|
||||
else {
|
||||
resolvableSdks.add(new MissingSdkInfo(sdkName, sdkType));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void applyDownloadableFix(@NotNull UnknownSdk info, @NotNull DownloadSdkFix fix) {
|
||||
@@ -153,14 +173,15 @@ public class UnknownSdkTracker {
|
||||
});
|
||||
}
|
||||
|
||||
public void showSdkSelectionPopup(@NotNull UnknownSdk info,
|
||||
@NotNull JComponent panel,
|
||||
public void showSdkSelectionPopup(@NotNull String sdkName,
|
||||
@Nullable SdkType sdkType,
|
||||
@NotNull Container underneathRightOfComponent,
|
||||
@NotNull Runnable onSelectionMade) {
|
||||
ProjectSdksModel model = new ProjectSdksModel();
|
||||
SdkListModelBuilder modelBuilder = new SdkListModelBuilder(
|
||||
myProject,
|
||||
model,
|
||||
sdkType -> Objects.equals(sdkType, info.getSdkType()),
|
||||
sdkType != null ? type -> Objects.equals(type, sdkType) : null,
|
||||
null,
|
||||
null);
|
||||
|
||||
@@ -175,16 +196,13 @@ public class UnknownSdkTracker {
|
||||
@Override
|
||||
public void sdkAdded(@NotNull Sdk sdk) {
|
||||
//it is easier and safer than committing the ProjectSdksModel instance
|
||||
registerNewSdkInJdkTable(info, sdk);
|
||||
registerNewSdkInJdkTable(sdkName, sdk);
|
||||
wasSdkCreated.set(true);
|
||||
}
|
||||
});
|
||||
|
||||
//FileEditorManager#addTopComponent wraps the panel to implement borders, unwrapping
|
||||
Container container = panel.getParent();
|
||||
if (container == null) container = panel;
|
||||
popup.showUnderneathToTheRightOf(
|
||||
container,
|
||||
underneathRightOfComponent,
|
||||
() -> {
|
||||
if (wasSdkCreated.get()) {
|
||||
onSelectionMade.run();
|
||||
@@ -193,9 +211,9 @@ public class UnknownSdkTracker {
|
||||
);
|
||||
}
|
||||
|
||||
private static void configureLocalSdks(@NotNull Map<MissingSdkInfo, LocalSdkFix> localFixes) {
|
||||
for (Map.Entry<MissingSdkInfo, LocalSdkFix> e : localFixes.entrySet()) {
|
||||
MissingSdkInfo info = e.getKey();
|
||||
private static void configureLocalSdks(@NotNull Map<UnknownSdk, LocalSdkFix> localFixes) {
|
||||
for (Map.Entry<UnknownSdk, LocalSdkFix> e : localFixes.entrySet()) {
|
||||
UnknownSdk info = e.getKey();
|
||||
LocalSdkFix fix = e.getValue();
|
||||
|
||||
Sdk sdk = createSdkPrototype(info);
|
||||
@@ -216,32 +234,20 @@ public class UnknownSdkTracker {
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private static Map<MissingSdkInfo, LocalSdkFix> findLocalFixes(@NotNull ProgressIndicator indicator,
|
||||
@NotNull List<MissingSdkInfo> infos,
|
||||
@NotNull List<UnknownSdkLookup> lookups) {
|
||||
|
||||
Map<MissingSdkInfo, LocalSdkFix> result = new LinkedHashMap<>();
|
||||
for (MissingSdkInfo info : infos) {
|
||||
private static <R> Map<UnknownSdk, R> findFixesAndRemoveFixable(@NotNull ProgressIndicator indicator,
|
||||
@NotNull List<UnknownSdk> infos,
|
||||
@NotNull List<UnknownSdkLookup> lookups,
|
||||
@NotNull TripleFunction<UnknownSdkLookup, UnknownSdk, ProgressIndicator, R> fun) {
|
||||
Map<UnknownSdk, R> result = new LinkedHashMap<>();
|
||||
for (Iterator<UnknownSdk> iterator = infos.iterator(); iterator.hasNext(); ) {
|
||||
UnknownSdk info = iterator.next();
|
||||
for (UnknownSdkLookup lookup : lookups) {
|
||||
LocalSdkFix fix = lookup.proposeLocalFix(info, indicator);
|
||||
if (fix == null) continue;
|
||||
result.put(info, fix);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private static Map<MissingSdkInfo, DownloadSdkFix> findDownloadFixes(@NotNull ProgressIndicator indicator,
|
||||
@NotNull List<MissingSdkInfo> infos,
|
||||
@NotNull List<UnknownSdkLookup> lookups) {
|
||||
|
||||
Map<MissingSdkInfo, DownloadSdkFix> result = new LinkedHashMap<>();
|
||||
for (MissingSdkInfo info : infos) {
|
||||
for (UnknownSdkLookup lookup : lookups) {
|
||||
DownloadSdkFix fix = lookup.proposeDownload(info, indicator);
|
||||
if (fix == null) continue;
|
||||
result.put(info, fix);
|
||||
R fix = fun.fun(lookup, info, indicator);
|
||||
if (fix != null) {
|
||||
result.put(info, fix);
|
||||
iterator.remove();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
@@ -253,16 +259,20 @@ public class UnknownSdkTracker {
|
||||
}
|
||||
|
||||
private static void registerNewSdkInJdkTable(@NotNull UnknownSdk info, @NotNull Sdk sdk) {
|
||||
registerNewSdkInJdkTable(info.getSdkName(), sdk);
|
||||
}
|
||||
|
||||
private static void registerNewSdkInJdkTable(@NotNull String sdkName, @NotNull Sdk sdk) {
|
||||
WriteAction.run(() -> {
|
||||
ProjectJdkTable table = ProjectJdkTable.getInstance();
|
||||
Sdk clash = table.findJdk(info.getSdkName());
|
||||
Sdk clash = table.findJdk(sdkName);
|
||||
if (clash != null) {
|
||||
LOG.warn("SDK with name " + info.getSdkName() + " already exists: clash=" + clash + ", new=" + sdk);
|
||||
LOG.warn("SDK with name " + sdkName + " already exists: clash=" + clash + ", new=" + sdk);
|
||||
return;
|
||||
}
|
||||
|
||||
SdkModificator mod = sdk.getSdkModificator();
|
||||
mod.setName(info.getSdkName());
|
||||
mod.setName(sdkName);
|
||||
mod.commitChanges();
|
||||
|
||||
table.addJdk(sdk);
|
||||
@@ -271,26 +281,11 @@ public class UnknownSdkTracker {
|
||||
|
||||
private static class MissingSdkInfo implements UnknownSdk {
|
||||
@NotNull private final String mySdkName;
|
||||
@NotNull private final Set<String> mySdkTypeNames = new HashSet<>(); // nullable keys are ok
|
||||
@Nullable private SdkType mySdkType;
|
||||
@NotNull private final SdkType mySdkType;
|
||||
|
||||
MissingSdkInfo(@NotNull String sdkName) {
|
||||
private MissingSdkInfo(@NotNull String sdkName, @NotNull SdkType sdkType) {
|
||||
mySdkName = sdkName;
|
||||
}
|
||||
|
||||
void attachSdkType(@Nullable String name) {
|
||||
if (!mySdkTypeNames.add(name)) return; //null is ok here
|
||||
|
||||
if (name == null || mySdkTypeNames.size() != 1) {
|
||||
mySdkType = null;
|
||||
}
|
||||
else {
|
||||
for (SdkType type : SdkType.getAllTypes()) {
|
||||
if (type.getName().equals(name) && type.allowCreationByUser()) {
|
||||
mySdkType = type;
|
||||
}
|
||||
}
|
||||
}
|
||||
mySdkType = sdkType;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@@ -302,7 +297,6 @@ public class UnknownSdkTracker {
|
||||
@NotNull
|
||||
@Override
|
||||
public SdkType getSdkType() {
|
||||
assert mySdkType != null;
|
||||
return mySdkType;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user