class LanguageComponentsLoader {

  public static Map<String, List<LanguageComponentsProvider>> loadProvidersFromSpi(
      ClassLoader... classloaders) {
    Map<String, List<LanguageComponentsProvider>> providers = new TreeMap<>();

    BiPredicate<LanguageComponentsProvider, LanguageComponentsProvider> sameProvider =
        (p1, p2) -> {
          return Objects.equals(p1.name(), p2.name())
              && Objects.equals(p1.getClass(), p2.getClass())
              && Objects.equals(p1.componentTypes(), p2.componentTypes())
              && Objects.equals(p1.languages(), p2.languages());
        };

    for (ClassLoader classLoader : classloaders) {
      for (LanguageComponentsProvider candidate :
          ServiceLoader.load(LanguageComponentsProvider.class, classLoader)) {
        for (String language : candidate.languages()) {
          List<LanguageComponentsProvider> existingProviders =
              providers.computeIfAbsent(language, key -> new ArrayList<>());

          if (existingProviders.stream()
              .noneMatch(existing -> sameProvider.test(existing, candidate))) {
            existingProviders.add(candidate);
          }
        }
      }
    }

    return providers;
  }

}