private static Graph<PluginId> createPluginIdGraph(
      final Map<PluginId, IdeaPluginDescriptorImpl> idToDescriptorMap) {
    final List<PluginId> ids = new ArrayList<PluginId>(idToDescriptorMap.keySet());
    // this magic ensures that the dependent plugins always follow their dependencies in
    // lexicographic order
    // needed to make sure that extensions are always in the same order
    Collections.sort(
        ids,
        new Comparator<PluginId>() {
          @Override
          public int compare(PluginId o1, PluginId o2) {
            return o2.getIdString().compareTo(o1.getIdString());
          }
        });
    return GraphGenerator.create(
        CachingSemiGraph.create(
            new GraphGenerator.SemiGraph<PluginId>() {
              @Override
              public Collection<PluginId> getNodes() {
                return ids;
              }

              @Override
              public Iterator<PluginId> getIn(PluginId pluginId) {
                final IdeaPluginDescriptor descriptor = idToDescriptorMap.get(pluginId);
                ArrayList<PluginId> plugins = new ArrayList<PluginId>();
                for (PluginId dependentPluginId : descriptor.getDependentPluginIds()) {
                  // check for missing optional dependency
                  if (idToDescriptorMap.containsKey(dependentPluginId)) {
                    plugins.add(dependentPluginId);
                  }
                }
                return plugins.iterator();
              }
            }));
  }
  @Nullable
  private static Pair<List<String>, Boolean> keysOrder(final ResourceBundle resourceBundle) {
    final boolean[] isAlphaSorted = new boolean[] {true};
    final Graph<String> generator =
        GraphGenerator.generate(
            CachingSemiGraph.cache(
                new InboundSemiGraph<String>() {
                  @Override
                  public Collection<String> getNodes() {
                    final Set<String> nodes = new LinkedHashSet<>();
                    for (PropertiesFile propertiesFile : resourceBundle.getPropertiesFiles()) {
                      for (IProperty property : propertiesFile.getProperties()) {
                        final String key = property.getKey();
                        if (key != null) {
                          nodes.add(key);
                        }
                      }
                    }
                    return nodes;
                  }

                  @Override
                  public Iterator<String> getIn(String n) {
                    final Collection<String> siblings = new LinkedHashSet<>();
                    for (PropertiesFile propertiesFile : resourceBundle.getPropertiesFiles()) {
                      for (IProperty property : propertiesFile.findPropertiesByKey(n)) {
                        PsiElement sibling = property.getPsiElement().getNextSibling();
                        while (sibling instanceof PsiWhiteSpace || sibling instanceof PsiComment) {
                          sibling = sibling.getNextSibling();
                        }
                        if (sibling instanceof IProperty) {
                          final String key = ((IProperty) sibling).getKey();
                          if (key != null) {
                            if (isAlphaSorted[0]
                                && String.CASE_INSENSITIVE_ORDER.compare(n, key) > 0) {
                              isAlphaSorted[0] = false;
                            }
                            siblings.add(key);
                          }
                        }
                      }
                    }
                    return siblings.iterator();
                  }
                }));
    DFSTBuilder<String> dfstBuilder = new DFSTBuilder<>(generator);
    final boolean acyclic = dfstBuilder.isAcyclic();
    if (acyclic) {
      if (isAlphaSorted[0]) {
        final List<String> sortedNodes = new ArrayList<>(generator.getNodes());
        Collections.sort(sortedNodes, String.CASE_INSENSITIVE_ORDER);
        return Pair.create(sortedNodes, true);
      } else {
        final List<String> dfsNodes = dfstBuilder.getSortedNodes();
        Collections.reverse(dfsNodes);
        return Pair.create(dfsNodes, false);
      }
    } else {
      return null;
    }
  }