public static <T extends PluginInfo> void unloadIncompatiblePlugins(Map<String, T> pluginsByKey) {
    // loop as long as the previous loop ignored some plugins. That allows to support dependencies
    // on many levels, for example D extends C, which extends B, which requires A. If A is not installed,
    // then B, C and D must be ignored. That's not possible to achieve this algorithm with a single iteration over plugins.
    var validator = new PluginRequirementsValidator<>(pluginsByKey);
    Set<String> removedKeys = new HashSet<>();
    do {
      removedKeys.clear();
      for (T plugin : pluginsByKey.values()) {
        if (!validator.isCompatible(plugin)) {
          removedKeys.add(plugin.getKey());
        }
      }
      for (String removedKey : removedKeys) {
        pluginsByKey.remove(removedKey);
      }
    } while (!removedKeys.isEmpty());
  }
@Test
  public void unloadIncompatiblePlugins_removes_incompatible_plugins() {
    PluginInfo pluginE = new PluginInfo("pluginE");

    PluginInfo pluginD = new PluginInfo("pluginD")
      .setBasePlugin("pluginC");
    PluginInfo pluginC = new PluginInfo("pluginC")
      .setBasePlugin("pluginB");
    PluginInfo pluginB = new PluginInfo("pluginB")
      .addRequiredPlugin(RequiredPlugin.parse("pluginA:1.0"));
    Map<String, PluginInfo> plugins = new HashMap<>();
    plugins.put(pluginB.getKey(), pluginB);
    plugins.put(pluginC.getKey(), pluginC);
    plugins.put(pluginD.getKey(), pluginD);
    plugins.put(pluginE.getKey(), pluginE);

    PluginRequirementsValidator.unloadIncompatiblePlugins(plugins);

    assertThat(plugins).contains(Map.entry(pluginE.getKey(), pluginE));
  }