diff --git a/java/java-tests/testSrc/com/intellij/java/codeInsight/daemon/impl/SdkSetupNotificationTest.java b/java/java-tests/testSrc/com/intellij/java/codeInsight/daemon/impl/SdkSetupNotificationTest.java index 089798eb514a..d63957385928 100644 --- a/java/java-tests/testSrc/com/intellij/java/codeInsight/daemon/impl/SdkSetupNotificationTest.java +++ b/java/java-tests/testSrc/com/intellij/java/codeInsight/daemon/impl/SdkSetupNotificationTest.java @@ -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 fixes = detectMissingSdks(); + assertThat(fixes) + .withFailMessage(String.valueOf(fixes)) + .isEmpty(); + } + + public void testMissingProjectJdk() { + WriteAction.run(() -> { + ProjectRootManager.getInstance(getProject()).setProjectSdkName("missingSDK", JavaSdk.getInstance().getName()); + }); + + final List 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 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 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 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 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 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 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); + }); } } diff --git a/platform/lang-impl/src/com/intellij/openapi/projectRoots/impl/UnknownSdkBalloonNotification.java b/platform/lang-impl/src/com/intellij/openapi/projectRoots/impl/UnknownSdkBalloonNotification.java index 572f8e4c9b36..71260192a0a4 100644 --- a/platform/lang-impl/src/com/intellij/openapi/projectRoots/impl/UnknownSdkBalloonNotification.java +++ b/platform/lang-impl/src/com/intellij/openapi/projectRoots/impl/UnknownSdkBalloonNotification.java @@ -33,10 +33,11 @@ public class UnknownSdkBalloonNotification { } public void notifyFixedSdks(@NotNull Map localFixes) { + if (localFixes.isEmpty()) return; + final String title; final String change; final StringBuilder message = new StringBuilder(); - if (localFixes.isEmpty()) return; Set usages = new TreeSet<>(); for (Map.Entry entry : localFixes.entrySet()) { diff --git a/platform/lang-impl/src/com/intellij/openapi/projectRoots/impl/UnknownSdkEditorNotification.java b/platform/lang-impl/src/com/intellij/openapi/projectRoots/impl/UnknownSdkEditorNotification.java index 3abc03f89a7d..f356c418061c 100644 --- a/platform/lang-impl/src/com/intellij/openapi/projectRoots/impl/UnknownSdkEditorNotification.java +++ b/platform/lang-impl/src/com/intellij/openapi/projectRoots/impl/UnknownSdkEditorNotification.java @@ -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> 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 myNotifications = new HashMap<>(); + private final Map 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 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 files) { - myNotifications = new HashMap<>(files); + @NotNull + public List getNotifications() { + return ImmutableList.copyOf(myNotifications.values()); + } + + public void showNotifications(@NotNull List unifiableSdkNames, + @NotNull Map files) { + myNotifications.clear(); + + for (String name : unifiableSdkNames) { + myNotifications.put(name, SdkFixInfo.forBroken(name)); + } + + for (Map.Entry 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 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 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 e) { + UnknownSdk sdk = e.getKey(); + DownloadSdkFix fix = e.getValue(); + return new SdkFixInfo(sdk.getSdkName()) { + @NotNull + @Override + public Pair 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 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 + '\'' + + '}'; + } + } } diff --git a/platform/lang-impl/src/com/intellij/openapi/projectRoots/impl/UnknownSdkTracker.java b/platform/lang-impl/src/com/intellij/openapi/projectRoots/impl/UnknownSdkTracker.java index 175749330c5e..732538ca0f62 100644 --- a/platform/lang-impl/src/com/intellij/openapi/projectRoots/impl/UnknownSdkTracker.java +++ b/platform/lang-impl/src/com/intellij/openapi/projectRoots/impl/UnknownSdkTracker.java @@ -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 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 lookups) { - List fixable = ReadAction.compute(() -> collectUnknownSdks()); - if (fixable.isEmpty()) return; + private void updateUnknownSdksWithProgress(@NotNull ProgressIndicator indicator) { + List totallyUnknownSdks = new ArrayList<>(); + List fixable = new ArrayList<>(); + ReadAction.run(() -> collectAndGroupSdkUsages(totallyUnknownSdks, fixable)); - Map localFixes = findLocalFixes(indicator, fixable, lookups); - Map downloadFixes = findDownloadFixes(indicator, - ContainerUtil.filter(fixable, info -> !localFixes.containsKey(info)), - lookups); - - if (localFixes.isEmpty() && downloadFixes.isEmpty()) return; + List lookups = collectSdkLookups(indicator); + Map localFixes = findFixesAndRemoveFixable(indicator, fixable, lookups, UnknownSdkLookup::proposeLocalFix); + Map 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 collectUnknownSdks() { - Map myInfos = new HashMap<>(); - List usages = SdkUsagesCollector.getInstance(myProject).collectSdkUsages(); + private List collectSdkLookups(@NotNull ProgressIndicator indicator) { + List 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 totallyUnknownSdks, + @NotNull List resolvableSdks) { + SetMultimap 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> entry : sdkToTypes.asMap().entrySet()) { + Collection 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 localFixes) { - for (Map.Entry e : localFixes.entrySet()) { - MissingSdkInfo info = e.getKey(); + private static void configureLocalSdks(@NotNull Map localFixes) { + for (Map.Entry 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 findLocalFixes(@NotNull ProgressIndicator indicator, - @NotNull List infos, - @NotNull List lookups) { - - Map result = new LinkedHashMap<>(); - for (MissingSdkInfo info : infos) { + private static Map findFixesAndRemoveFixable(@NotNull ProgressIndicator indicator, + @NotNull List infos, + @NotNull List lookups, + @NotNull TripleFunction fun) { + Map result = new LinkedHashMap<>(); + for (Iterator 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 findDownloadFixes(@NotNull ProgressIndicator indicator, - @NotNull List infos, - @NotNull List lookups) { - - Map 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 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; }