<commit_before>// Copyright 2000-2020 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.ApplicationManager;
import com.intellij.openapi.application.WriteAction;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.progress.ProgressIndicator;
import com.intellij.openapi.progress.ProgressManager;
import com.intellij.openapi.progress.Task;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.project.ProjectBundle;
import com.intellij.openapi.projectRoots.*;
import com.intellij.openapi.roots.ui.configuration.*;
import com.intellij.openapi.roots.ui.configuration.UnknownSdkResolver.UnknownSdkLookup;
import com.intellij.openapi.util.io.FileUtil;
import com.intellij.openapi.util.registry.Registry;
import com.intellij.ui.EditorNotificationPanel;
import com.intellij.util.Consumer;
import com.intellij.util.TripleFunction;
import com.intellij.util.ui.update.MergingUpdateQueue;
import com.intellij.util.ui.update.Update;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.util.*;

import static com.intellij.openapi.progress.PerformInBackgroundOption.ALWAYS_BACKGROUND;

public class UnknownSdkTracker {
  private static final Logger LOG = Logger.getInstance(UnknownSdkTracker.class);

  @NotNull
  public static UnknownSdkTracker getInstance(@NotNull Project project) {
    return project.getService(UnknownSdkTracker.class);
  }

  @NotNull private final Project myProject;
  @NotNull private final MergingUpdateQueue myUpdateQueue;

  private UnknownSdkSnapshot myPreviousRequestCache = null;

  public UnknownSdkTracker(@NotNull Project project) {
    myProject = project;
    myUpdateQueue = new MergingUpdateQueue(getClass().getSimpleName(),
                                           700,
                                           true,
                                           null,
                                           myProject,
                                           null,
                                           false)
      .usePassThroughInUnitTestMode();
  }

  @NotNull
  private Update newUpdateTask() {
    return new Update("update") {
      @Override
      public void run() {
        if (!Registry.is("unknown.sdk") || !UnknownSdkResolver.EP_NAME.hasAnyExtensions()) {
          showStatus(Collections.emptyList(), Collections.emptyMap(), Collections.emptyMap(), Collections.emptyList());
          return;
        }

        new UnknownSdkCollector(myProject)
          .collectSdksPromise(snapshot -> {

            //there is nothing to do if we see the same snapshot, IDEA-236153
            if (snapshot.equals(myPreviousRequestCache)) return;
            myPreviousRequestCache = snapshot;

            //we cannot use snapshot#missingSdks here, because it affects other IDEs/languages where our logic is not good enough
            onFixableAndMissingSdksCollected(filterOnlyAllowedEntries(snapshot.getResolvableSdks()), filterOnlyAllowedSdkEntries(snapshot.getKnownSdks()));
          });
      }
    };
  }

  public void updateUnknownSdksNow() {
    myUpdateQueue.run(newUpdateTask());
  }

  public void updateUnknownSdks() {
    myUpdateQueue.queue(newUpdateTask());
  }

  private static boolean allowFixesFor(@NotNull SdkTypeId type) {
    return UnknownSdkResolver.EP_NAME.findFirstSafe(it -> it.supportsResolution(type)) != null;
  }

  @NotNull
  private static <E extends UnknownSdk> List<E> filterOnlyAllowedEntries(@NotNull List<? extends E> input) {
    List<E> copy = new ArrayList<>();
    for (E item : input) {
      SdkType type = item.getSdkType();

      if (allowFixesFor(type)) {
        copy.add(item);
      }
    }

    return copy;
  }

  @NotNull
  private static List<Sdk> filterOnlyAllowedSdkEntries(@NotNull List<Sdk> input) {
    List<Sdk> copy = new ArrayList<>();
    for (Sdk item : input) {
      SdkTypeId type = item.getSdkType();

      if (allowFixesFor(type)) {
        copy.add(item);
      }
    }

    return copy;
  }

  private void onFixableAndMissingSdksCollected(@NotNull List<UnknownSdk> fixable, @NotNull List<Sdk> usedSdks) {
    if (fixable.isEmpty() && usedSdks.isEmpty()) {
      showStatus(Collections.emptyList(), Collections.emptyMap(), Collections.emptyMap(), Collections.emptyList());
      return;
    }

    ProgressManager.getInstance()
      .run(new Task.Backgroundable(myProject, ProjectBundle.message("progress.title.resolving.sdks"), false, ALWAYS_BACKGROUND) {
             @Override
             public void run(@NotNull ProgressIndicator indicator) {
               indicator.setText(ProjectBundle.message("progress.text.resolving.missing.sdks"));
               List<UnknownSdkLookup> lookups = collectSdkLookups(indicator);

               indicator.setText(ProjectBundle.message("progress.text.resolving.existing.sdks"));
               List<UnknownInvalidSdk> invalidSdks = UnknownInvalidSdk.resolveInvalidSdks(usedSdks);
               fixable.addAll(invalidSdks);

               indicator.setText(ProjectBundle.message("progress.text.looking.for.local.sdks"));
               Map<UnknownSdk, UnknownSdkLocalSdkFix> localFixes = findFixesAndRemoveFixable(indicator, fixable, lookups, UnknownSdkLookup::proposeLocalFix);

               indicator.setText(ProjectBundle.message("progress.text.looking.for.downloadable.sdks"));
               Map<UnknownSdk, UnknownSdkDownloadableSdkFix> downloadFixes = findFixesAndRemoveFixable(indicator, fixable, lookups, UnknownSdkLookup::proposeDownload);

               UnknownInvalidSdk.removeAndUpdate(invalidSdks, fixable, localFixes, downloadFixes);

               if (!localFixes.isEmpty()) {
                 indicator.setText(ProjectBundle.message("progress.text.configuring.sdks"));
                 configureLocalSdks(localFixes);
               }

               showStatus(fixable, localFixes, downloadFixes, invalidSdks);
             }
           }
      );
  }

  private void showStatus(@NotNull List<UnknownSdk> unknownSdksWithoutFix,
                          @NotNull Map<UnknownSdk, UnknownSdkLocalSdkFix> localFixes,
                          @NotNull Map<UnknownSdk, UnknownSdkDownloadableSdkFix> downloadFixes,
                          @NotNull List<UnknownInvalidSdk> invalidSdks) {
    UnknownSdkBalloonNotification
      .getInstance(myProject)
      .notifyFixedSdks(localFixes);

    UnknownSdkEditorNotification
      .getInstance(myProject)
      .showNotifications(unknownSdksWithoutFix, downloadFixes, invalidSdks);<commit_msg>UnknownSdkTracker - do less work and less progress updates if possible<commit_after>// Copyright 2000-2020 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.ApplicationManager;
import com.intellij.openapi.application.WriteAction;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.progress.ProgressIndicator;
import com.intellij.openapi.progress.ProgressManager;
import com.intellij.openapi.progress.Task;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.project.ProjectBundle;
import com.intellij.openapi.projectRoots.*;
import com.intellij.openapi.roots.ui.configuration.*;
import com.intellij.openapi.roots.ui.configuration.UnknownSdkResolver.UnknownSdkLookup;
import com.intellij.openapi.util.io.FileUtil;
import com.intellij.openapi.util.registry.Registry;
import com.intellij.ui.EditorNotificationPanel;
import com.intellij.util.Consumer;
import com.intellij.util.TripleFunction;
import com.intellij.util.ui.update.MergingUpdateQueue;
import com.intellij.util.ui.update.Update;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.util.*;

import static com.intellij.openapi.progress.PerformInBackgroundOption.ALWAYS_BACKGROUND;

public class UnknownSdkTracker {
  private static final Logger LOG = Logger.getInstance(UnknownSdkTracker.class);

  @NotNull
  public static UnknownSdkTracker getInstance(@NotNull Project project) {
    return project.getService(UnknownSdkTracker.class);
  }

  @NotNull private final Project myProject;
  @NotNull private final MergingUpdateQueue myUpdateQueue;

  private UnknownSdkSnapshot myPreviousRequestCache = null;

  public UnknownSdkTracker(@NotNull Project project) {
    myProject = project;
    myUpdateQueue = new MergingUpdateQueue(getClass().getSimpleName(),
                                           700,
                                           true,
                                           null,
                                           myProject,
                                           null,
                                           false)
      .usePassThroughInUnitTestMode();
  }

  @NotNull
  private Update newUpdateTask() {
    return new Update("update") {
      @Override
      public void run() {
        if (!Registry.is("unknown.sdk") || !UnknownSdkResolver.EP_NAME.hasAnyExtensions()) {
          showStatus(Collections.emptyList(), Collections.emptyMap(), Collections.emptyMap(), Collections.emptyList());
          return;
        }

        new UnknownSdkCollector(myProject)
          .collectSdksPromise(snapshot -> {

            //there is nothing to do if we see the same snapshot, IDEA-236153
            if (snapshot.equals(myPreviousRequestCache)) return;
            myPreviousRequestCache = snapshot;

            //we cannot use snapshot#missingSdks here, because it affects other IDEs/languages where our logic is not good enough
            onFixableAndMissingSdksCollected(filterOnlyAllowedEntries(snapshot.getResolvableSdks()), filterOnlyAllowedSdkEntries(snapshot.getKnownSdks()));
          });
      }
    };
  }

  public void updateUnknownSdksNow() {
    myUpdateQueue.run(newUpdateTask());
  }

  public void updateUnknownSdks() {
    myUpdateQueue.queue(newUpdateTask());
  }

  private static boolean allowFixesFor(@NotNull SdkTypeId type) {
    return UnknownSdkResolver.EP_NAME.findFirstSafe(it -> it.supportsResolution(type)) != null;
  }

  @NotNull
  private static <E extends UnknownSdk> List<E> filterOnlyAllowedEntries(@NotNull List<? extends E> input) {
    List<E> copy = new ArrayList<>();
    for (E item : input) {
      SdkType type = item.getSdkType();

      if (allowFixesFor(type)) {
        copy.add(item);
      }
    }

    return copy;
  }

  @NotNull
  private static List<Sdk> filterOnlyAllowedSdkEntries(@NotNull List<Sdk> input) {
    List<Sdk> copy = new ArrayList<>();
    for (Sdk item : input) {
      SdkTypeId type = item.getSdkType();

      if (allowFixesFor(type)) {
        copy.add(item);
      }
    }

    return copy;
  }

  private void onFixableAndMissingSdksCollected(@NotNull List<UnknownSdk> fixable, @NotNull List<Sdk> usedSdks) {
    if (fixable.isEmpty() && usedSdks.isEmpty()) {
      showStatus(Collections.emptyList(), Collections.emptyMap(), Collections.emptyMap(), Collections.emptyList());
      return;
    }

    ProgressManager.getInstance()
      .run(new Task.Backgroundable(myProject, ProjectBundle.message("progress.title.resolving.sdks"), false, ALWAYS_BACKGROUND) {
             @Override
             public void run(@NotNull ProgressIndicator indicator) {
               List<UnknownInvalidSdk> invalidSdks = new ArrayList<>();
               Map<UnknownSdk, UnknownSdkLocalSdkFix> localFixes = new HashMap<>();
               Map<UnknownSdk, UnknownSdkDownloadableSdkFix> downloadFixes = new HashMap<>();

               if (!usedSdks.isEmpty()) {
                 indicator.pushState();
                 indicator.setText(ProjectBundle.message("progress.text.resolving.existing.sdks"));
                 invalidSdks = UnknownInvalidSdk.resolveInvalidSdks(usedSdks);
                 fixable.addAll(invalidSdks);
                 indicator.popState();
               }

               if (!fixable.isEmpty()) {
                 indicator.pushState();
                 indicator.setText(ProjectBundle.message("progress.text.resolving.missing.sdks"));
                 List<UnknownSdkLookup> lookups = collectSdkLookups(indicator);

                 if (!lookups.isEmpty()) {
                   indicator.setText(ProjectBundle.message("progress.text.looking.for.local.sdks"));
                   localFixes = findFixesAndRemoveFixable(indicator, fixable, lookups, UnknownSdkLookup::proposeLocalFix);

                   if (!fixable.isEmpty()) {
                     indicator.setText(ProjectBundle.message("progress.text.looking.for.downloadable.sdks"));
                     downloadFixes = findFixesAndRemoveFixable(indicator, fixable, lookups, UnknownSdkLookup::proposeDownload);
                   }
                 }

                 indicator.popState();
               }

               UnknownInvalidSdk.removeAndUpdate(invalidSdks, fixable, localFixes, downloadFixes);

               if (!localFixes.isEmpty()) {
                 indicator.pushState();
                 indicator.setText(ProjectBundle.message("progress.text.configuring.sdks"));
                 configureLocalSdks(localFixes);
                 indicator.popState();
               }

               showStatus(fixable, localFixes, downloadFixes, invalidSdks);
             }
           }
      );
  }

  private void showStatus(@NotNull List<UnknownSdk> unknownSdksWithoutFix,
                          @NotNull Map<UnknownSdk, UnknownSdkLocalSdkFix> localFixes,
                          @NotNull Map<UnknownSdk, UnknownSdkDownloadableSdkFix> downloadFixes,
                          @NotNull List<UnknownInvalidSdk> invalidSdks) {
    UnknownSdkBalloonNotification
      .getInstance(myProject)
      .notifyFixedSdks(localFixes);

    UnknownSdkEditorNotification
      .getInstance(myProject)
      .showNotifications(unknownSdksWithoutFix, downloadFixes, invalidSdks);
{'fraction_non_alphanumeric': 0.09531559581092557, 'fraction_numerical': 0.00268893291819983, 'mean_word_length': 2.9030654515327257, 'pattern_counts': {'":': 0, '<': 44, '<?xml version=': 0, '>': 48, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 43, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '3113815', 'n_tokens_mistral': 3924, 'n_tokens_neox': 3813, 'n_words': 709}