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 d63957385928..ac0c546d5c07 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,6 +1,7 @@ // 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.openapi.application.ApplicationManager; import com.intellij.openapi.application.WriteAction; import com.intellij.openapi.projectRoots.JavaSdk; import com.intellij.openapi.projectRoots.ProjectJdkTable; @@ -14,6 +15,8 @@ import com.intellij.openapi.roots.ModuleRootModificationUtil; import com.intellij.openapi.roots.ProjectRootManager; import com.intellij.testFramework.IdeaTestUtil; import com.intellij.testFramework.fixtures.JavaCodeInsightFixtureTestCase; +import com.intellij.util.ThrowableRunnable; +import org.jetbrains.annotations.Contract; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -45,9 +48,7 @@ public class SdkSetupNotificationTest extends JavaCodeInsightFixtureTestCase { final List fixes = detectMissingSdks(); assertThat(fixes) .withFailMessage(String.valueOf(fixes)) - .hasSize(1) - .first() - .returns("missingSDK", SdkFixInfo::getSdkName); + .hasSize(1); } public void testMissingModuleJdk() { @@ -60,9 +61,7 @@ public class SdkSetupNotificationTest extends JavaCodeInsightFixtureTestCase { final List fixes = detectMissingSdks(); assertThat(fixes) .withFailMessage(String.valueOf(fixes)) - .hasSize(1) - .first() - .returns("missingSDK", SdkFixInfo::getSdkName); + .hasSize(1); } public void testNoProjectSdk() { @@ -73,20 +72,10 @@ public class SdkSetupNotificationTest extends JavaCodeInsightFixtureTestCase { 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() { - Sdk sdk = IdeaTestUtil.getMockJdk18(); + Sdk sdk = addSdkIfNeeded(IdeaTestUtil.getMockJdk18()); ModuleRootModificationUtil.setModuleSdk(getModule(), sdk); final List fixes = detectMissingSdks(); @@ -102,15 +91,6 @@ public class SdkSetupNotificationTest extends JavaCodeInsightFixtureTestCase { 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 @@ -129,16 +109,29 @@ public class SdkSetupNotificationTest extends JavaCodeInsightFixtureTestCase { private void setProjectSdk(@Nullable Sdk sdk) { WriteAction.run(() -> { - ProjectJdkTable jdkTable = ProjectJdkTable.getInstance(); - - if (sdk != null) { - final Sdk foundJdk = jdkTable.findJdk(sdk.getName()); - if (foundJdk == null) { - jdkTable.addJdk(sdk, myFixture.getProjectDisposable()); - } - } - - ProjectRootManager.getInstance(getProject()).setProjectSdk(sdk); + ProjectRootManager.getInstance(getProject()).setProjectSdk(addSdkIfNeeded(sdk)); }); } + + @Contract("null->null;!null->!null") + private Sdk addSdkIfNeeded(Sdk sdk) { + if (sdk == null) return null; + + ProjectJdkTable jdkTable = ProjectJdkTable.getInstance(); + + final Sdk foundJdk = jdkTable.findJdk(sdk.getName()); + if (foundJdk != null) return sdk; + + ThrowableRunnable addSdk = () -> { + jdkTable.addJdk(sdk, myFixture.getProjectDisposable()); + }; + + if (ApplicationManager.getApplication().isWriteAccessAllowed()) { + addSdk.run(); + } else { + WriteAction.run(addSdk); + } + + return sdk; + } } diff --git a/platform/lang-impl/src/com/intellij/openapi/projectRoots/impl/SdkUsagesCollector.java b/platform/lang-impl/src/com/intellij/openapi/projectRoots/impl/SdkUsagesCollector.java index b1d4a0be4513..2e1a04dce2ae 100644 --- a/platform/lang-impl/src/com/intellij/openapi/projectRoots/impl/SdkUsagesCollector.java +++ b/platform/lang-impl/src/com/intellij/openapi/projectRoots/impl/SdkUsagesCollector.java @@ -1,16 +1,17 @@ // 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.intellij.openapi.application.WriteAction; +import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.extensions.ExtensionPointName; import com.intellij.openapi.module.Module; import com.intellij.openapi.module.ModuleManager; import com.intellij.openapi.project.Project; -import com.intellij.openapi.roots.ModuleJdkOrderEntry; -import com.intellij.openapi.roots.ModuleRootManager; -import com.intellij.openapi.roots.OrderEntry; -import com.intellij.openapi.roots.ProjectRootManager; +import com.intellij.openapi.projectRoots.Sdk; +import com.intellij.openapi.roots.*; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import java.util.function.Consumer; import java.util.ArrayList; import java.util.Collections; @@ -39,16 +40,31 @@ public class SdkUsagesCollector { List contributeUsages(@NotNull Project project); } - public static class SdkUsage { + public static final class SdkUsage { + private final String myUsagePresentableText; private final String mySdkName; private final String mySdkTypeName; + private Consumer mySdkSetAction = null; + private Runnable myProjectSdkSetAction = null; - public SdkUsage(@NotNull String sdkName, @Nullable String sdkTypeName) { + public SdkUsage(@NotNull String usagePresentableText, + @Nullable String sdkName, + @Nullable String sdkTypeName) { + myUsagePresentableText = usagePresentableText; mySdkName = sdkName; mySdkTypeName = sdkTypeName; } + /** + * @return UI friendly description of the SDK-using entity, + * e.g. Project SDK or Module SDK + */ @NotNull + public String getUsagePresentableText() { + return myUsagePresentableText; + } + + @Nullable public String getSdkName() { return mySdkName; } @@ -57,6 +73,45 @@ public class SdkUsagesCollector { public String getSdkTypeName() { return mySdkTypeName; } + + /** + * @return an action to change the currently selected SDK + * to a given SDK. Return {@code null} to disallow that action + */ + @Nullable + public Consumer getSdkSetAction() { + return mySdkSetAction; + } + + /** + * Sets the action for {@link #getSdkSetAction()} + * @see #getSdkSetAction() + */ + @NotNull + public SdkUsage withSetSdkAction(@NotNull Consumer setSdkAction) { + mySdkSetAction = setSdkAction; + return this; + } + + /** + * @return an action to change the currently selected SDK + * to a project SDK. The action should work no matter if project SDK is valid or not. + * Return {@code null} to diasllow that action + */ + @Nullable + public Runnable getProjectSdkSetAction() { + return myProjectSdkSetAction; + } + + /** + * Sets the action for {@link #getProjectSdkSetAction()} + * @see #getProjectSdkSetAction() + */ + @NotNull + public SdkUsage withSetProjectSdkAction(@NotNull Runnable setProjectSdkAction) { + myProjectSdkSetAction = setProjectSdkAction; + return this; + } } /** @@ -77,10 +132,10 @@ public class SdkUsagesCollector { String sdkName = manager.getProjectSdkName(); String sdkType = manager.getProjectSdkTypeName(); - if (sdkName != null) { - return Collections.singletonList(new SdkUsage(sdkName, sdkType)); - } - return Collections.emptyList(); + SdkUsage usage = new SdkUsage("Project SDK", sdkName, sdkType) + .withSetSdkAction(sdk -> WriteAction.run(() -> ProjectRootManager.getInstance(project).setProjectSdk(sdk))); + + return Collections.singletonList(usage); } } @@ -90,18 +145,48 @@ public class SdkUsagesCollector { public List contributeUsages(@NotNull Project project) { List usages = new ArrayList<>(); for (Module module : ModuleManager.getInstance(project).getModules()) { - ModuleRootManager manager = ModuleRootManager.getInstance(module); - for (OrderEntry orderEntry : manager.getOrderEntries()) { - if (orderEntry instanceof ModuleJdkOrderEntry) { - String jdkName = ((ModuleJdkOrderEntry)orderEntry).getJdkName(); - String jdkType = ((ModuleJdkOrderEntry)orderEntry).getJdkTypeName(); - if (jdkName != null) { - usages.add(new SdkUsage(jdkName, jdkType)); - } - } - } + contributeForModule(module, usages); } return usages; } + + private void contributeForModule(@NotNull Module module, @NotNull List usages) { + ModuleRootManager manager = ModuleRootManager.getInstance(module); + String jdkName = null; + String jdkType = null; + + for (OrderEntry orderEntry : manager.getOrderEntries()) { + if (!(orderEntry instanceof JdkOrderEntry)) continue; + + if (orderEntry instanceof InheritedJdkOrderEntry) return; + if (orderEntry instanceof ModuleJdkOrderEntry) { + jdkName = ((ModuleJdkOrderEntry)orderEntry).getJdkName(); + jdkType = ((ModuleJdkOrderEntry)orderEntry).getJdkTypeName(); + continue; + } + + Logger.getInstance(getClass()).error("Unexpected OrderEntry: " + orderEntry.getClass().getName() + ": " + orderEntry); + } + + SdkUsage usage = new SdkUsage("module \"" + module.getName() + "\"", jdkName, jdkType) + .withSetProjectSdkAction(() -> { + WriteAction.run(() -> { + if (module.isDisposed()) return; + ModifiableRootModel mod = ModuleRootManager.getInstance(module).getModifiableModel(); + mod.inheritSdk(); + mod.commit(); + }); + }) + .withSetSdkAction(sdk -> { + WriteAction.run(() -> { + if (module.isDisposed()) return; + ModifiableRootModel mod = ModuleRootManager.getInstance(module).getModifiableModel(); + mod.setSdk(sdk); + mod.commit(); + }); + }); + + usages.add(usage); + } } } 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 f356c418061c..5b89b8ef6dbe 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 @@ -7,11 +7,12 @@ 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.Sdk; import com.intellij.openapi.projectRoots.SdkType; +import com.intellij.openapi.projectRoots.impl.SdkUsagesCollector.SdkUsage; 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; @@ -22,6 +23,7 @@ import javax.swing.*; import java.awt.*; import java.util.*; import java.util.List; +import java.util.function.Consumer; public class UnknownSdkEditorNotification implements Disposable { public static final Key> NOTIFICATIONS = Key.create("notifications added to the editor"); @@ -34,7 +36,7 @@ public class UnknownSdkEditorNotification implements Disposable { private final Project myProject; private final FileEditorManager myFileEditorManager; - private final Map myNotifications = new TreeMap<>(); + private final Set myNotifications = new LinkedHashSet<>(); UnknownSdkEditorNotification(@NotNull Project project) { myProject = project; @@ -51,23 +53,17 @@ public class UnknownSdkEditorNotification implements Disposable { }); } - @NotNull - private MissingSdkNotificationPanel createPanelFor(@NotNull SdkFixInfo info) { - Pair fixInfo = info.getFixInfo(); - String sdkName = info.getSdkName(); + private void setupPanel(@NotNull MissingSdkNotificationPanel panel, + @NotNull String sdkName, + @Nullable UnknownSdk unknownSdk, + @Nullable DownloadSdkFix fix) { - SdkType sdkType = fixInfo != null ? fixInfo.first.getSdkType() : null; + SdkType sdkType = unknownSdk != null ? unknownSdk.getSdkType() : null; String sdkTypeName = sdkType != null ? sdkType.getPresentableName() : "SDK"; - MissingSdkNotificationPanel panel = new MissingSdkNotificationPanel(info); - panel.setProject(myProject); - panel.setProviderKey(EDITOR_NOTIFICATIONS_KEY); panel.setText(sdkTypeName + " \"" + sdkName + "\" is missing"); - if (fixInfo != null) { - UnknownSdk unknownSdk = fixInfo.first; - DownloadSdkFix fix = fixInfo.second; - + if (fix != null && unknownSdk != null) { panel.createActionLabel("Download " + sdkTypeName + " (" + fix.getDownloadDescription() + ")", () -> { removeNotification(panel); UnknownSdkTracker.getInstance(myProject).applyDownloadableFix(unknownSdk, fix); @@ -85,8 +81,37 @@ public class UnknownSdkEditorNotification implements Disposable { .showSdkSelectionPopup(sdkName, sdkType, container, () -> removeNotification(panel)); } ); + } - return panel; + private void setupPanel(@NotNull MissingSdkNotificationPanel panel, + @NotNull String source, + @Nullable Runnable setProjectSdk, + @Nullable Consumer setSdk) { + + panel.setProviderKey(EDITOR_NOTIFICATIONS_KEY); + panel.setText("SDK is not set for " + source); + + if (setProjectSdk != null) { + panel.createActionLabel("Use Project SDK", () -> { + setProjectSdk.run(); + removeNotification(panel); + }); + } + + if (setSdk != null) { + 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(null, container, sdk -> { + setSdk.accept(sdk); + removeNotification(panel); + }); + }); + } } @Override @@ -94,19 +119,56 @@ public class UnknownSdkEditorNotification implements Disposable { @NotNull public List getNotifications() { - return ImmutableList.copyOf(myNotifications.values()); + return ImmutableList.copyOf(myNotifications); } - public void showNotifications(@NotNull List unifiableSdkNames, + public void showNotifications(@NotNull List unsetSdks, + @NotNull List unifiableSdkNames, @NotNull Map files) { myNotifications.clear(); + for (SdkUsage usage : unsetSdks) { + myNotifications.add(new SdkFixInfo() { + @Override + public void setupNotificationPanel(@NotNull MissingSdkNotificationPanel panel) { + setupPanel(panel, usage.getUsagePresentableText(), usage.getProjectSdkSetAction(), usage.getSdkSetAction()); + } + + @Override + public String toString() { + return "SdkFixInfo { sdkUsage: " + usage.getUsagePresentableText() + " }"; + } + }); + } + for (String name : unifiableSdkNames) { - myNotifications.put(name, SdkFixInfo.forBroken(name)); + myNotifications.add(new SdkFixInfo() { + @Override + public void setupNotificationPanel(@NotNull MissingSdkNotificationPanel panel) { + setupPanel(panel, name, (UnknownSdk)null, null); + } + + @Override + public String toString() { + return "SdkFixInfo { unknownName: '" + name + "' }"; + } + }); } for (Map.Entry e : files.entrySet()) { - myNotifications.put(e.getKey().getSdkName(), SdkFixInfo.forFix(e)); + UnknownSdk key = e.getKey(); + DownloadSdkFix fix = e.getValue(); + myNotifications.add(new SdkFixInfo() { + @Override + public void setupNotificationPanel(@NotNull MissingSdkNotificationPanel panel) { + setupPanel(panel, key.getSdkName(), key, fix); + } + + @Override + public String toString() { + return "SdkFixInfo { name: '" + key.getSdkName() + "', fix: " + fix.getDownloadDescription() + " }"; + } + }); } for (FileEditor editor : myFileEditorManager.getAllEditors()) { @@ -115,7 +177,7 @@ public class UnknownSdkEditorNotification implements Disposable { } private void removeNotification(@NotNull MissingSdkNotificationPanel expiredPanel) { - myNotifications.remove(expiredPanel.myInfo.getSdkName()); + myNotifications.remove(expiredPanel.myInfo); for (FileEditor editor : myFileEditorManager.getAllEditors()) { List notifications = editor.getUserData(NOTIFICATIONS); @@ -145,8 +207,11 @@ public class UnknownSdkEditorNotification implements Disposable { editor.putUserData(NOTIFICATIONS, notifications); } - for (SdkFixInfo info : myNotifications.values()) { - MissingSdkNotificationPanel notification = createPanelFor(info); + for (SdkFixInfo info : myNotifications) { + MissingSdkNotificationPanel notification = new MissingSdkNotificationPanel(info); + notification.setProject(myProject); + notification.setProviderKey(EDITOR_NOTIFICATIONS_KEY); + info.setupNotificationPanel(notification); notifications.add(notification); myFileEditorManager.addTopComponent(editor, notification); @@ -166,56 +231,7 @@ public class UnknownSdkEditorNotification implements Disposable { } } - 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 + '\'' + - '}'; - } + public interface SdkFixInfo { + void setupNotificationPanel(@NotNull MissingSdkNotificationPanel panel); } } 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 732538ca0f62..49abd7d2fca7 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,9 +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.google.common.collect.MultimapBuilder; +import com.google.common.collect.SetMultimap; 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; @@ -27,17 +27,16 @@ 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.Consumer; 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; -import javax.swing.*; import java.awt.*; import java.util.List; import java.util.*; -import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicReference; import static com.intellij.openapi.progress.PerformInBackgroundOption.ALWAYS_BACKGROUND; import static com.intellij.openapi.projectRoots.impl.UnknownSdkResolver.UnknownSdk; @@ -69,28 +68,28 @@ public class UnknownSdkTracker { } private void updateUnknownSdksWithProgress(@NotNull ProgressIndicator indicator) { - List totallyUnknownSdks = new ArrayList<>(); + List unsetSdks = new ArrayList<>(); + List missingSdks = new ArrayList<>(); List fixable = new ArrayList<>(); - ReadAction.run(() -> collectAndGroupSdkUsages(totallyUnknownSdks, fixable)); + ReadAction.run(() -> collectAndGroupSdkUsages(unsetSdks, missingSdks, fixable)); 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())); + fixable.forEach(it -> missingSdks.add(it.getSdkName())); ApplicationManager.getApplication().invokeLater(() -> { indicator.setText("Configuring SDKs..."); configureLocalSdks(localFixes); UnknownSdkBalloonNotification.getInstance(myProject).notifyFixedSdks(localFixes); - UnknownSdkEditorNotification.getInstance(myProject).showNotifications(totallyUnknownSdks, downloadFixes); + UnknownSdkEditorNotification.getInstance(myProject).showNotifications(unsetSdks, missingSdks, downloadFixes); }); } @NotNull private List collectSdkLookups(@NotNull ProgressIndicator indicator) { List lookups = new ArrayList<>(); - UnknownSdkResolver.EP_NAME.forEachExtensionSafe(ext -> { UnknownSdkLookup resolver = ext.createResolver(myProject, indicator); if (resolver != null) { @@ -100,7 +99,15 @@ public class UnknownSdkTracker { return lookups; } - private void collectAndGroupSdkUsages(@NotNull List totallyUnknownSdks, + /** + * Collects all SDK usages from the project model and splits them + * into the specified groups + * @param undefinedSdks all usages where SDK is not set, e.g. lack of Project or Module SDK + * @param totallyUnknownSdks all named SDKs that are not present and where SdkType is missing or contains different values + * @param resolvableSdks all usages where fix extension points from {@link UnknownSdkResolver#EP_NAME} are possible to apply + */ + private void collectAndGroupSdkUsages(@NotNull List undefinedSdks, + @NotNull List totallyUnknownSdks, @NotNull List resolvableSdks) { SetMultimap sdkToTypes = MultimapBuilder .treeKeys(String.CASE_INSENSITIVE_ORDER) @@ -111,6 +118,11 @@ public class UnknownSdkTracker { for (SdkUsage usage : SdkUsagesCollector.getInstance(myProject).collectSdkUsages()) { String sdkName = usage.getSdkName(); + if (sdkName == null) { + undefinedSdks.add(usage); + continue; + } + //we do not track existing SDKs if (jdkTable.findJdk(sdkName) != null) continue; @@ -177,6 +189,15 @@ public class UnknownSdkTracker { @Nullable SdkType sdkType, @NotNull Container underneathRightOfComponent, @NotNull Runnable onSelectionMade) { + showSdkSelectionPopup(sdkType, underneathRightOfComponent, sdk -> { + registerNewSdkInJdkTable(sdkName, sdk); + onSelectionMade.run(); + }); + } + + public void showSdkSelectionPopup(@Nullable SdkType sdkType, + @NotNull Container underneathRightOfComponent, + @NotNull Consumer onSelectionMade) { ProjectSdksModel model = new ProjectSdksModel(); SdkListModelBuilder modelBuilder = new SdkListModelBuilder( myProject, @@ -191,21 +212,22 @@ public class UnknownSdkTracker { modelBuilder ); - AtomicBoolean wasSdkCreated = new AtomicBoolean(false); + AtomicReference wasSdkCreated = new AtomicReference<>(null); model.addListener(new SdkModel.Listener() { @Override public void sdkAdded(@NotNull Sdk sdk) { + //TODO: handle if an existing item is selected! //it is easier and safer than committing the ProjectSdksModel instance - registerNewSdkInJdkTable(sdkName, sdk); - wasSdkCreated.set(true); + wasSdkCreated.set(sdk); } }); popup.showUnderneathToTheRightOf( underneathRightOfComponent, () -> { - if (wasSdkCreated.get()) { - onSelectionMade.run(); + Sdk sdk = wasSdkCreated.get(); + if (sdk != null) { + onSelectionMade.consume(sdk); } } ); @@ -299,18 +321,5 @@ public class UnknownSdkTracker { public SdkType getSdkType() { return mySdkType; } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (!(o instanceof MissingSdkInfo)) return false; - MissingSdkInfo info = (MissingSdkInfo)o; - return mySdkName.equals(info.mySdkName); - } - - @Override - public int hashCode() { - return Objects.hash(mySdkName); - } } }