class LinkageProblem {

  public static String formatLinkageProblems(
      Set<LinkageProblem> linkageProblems, @Nullable ClassPathResult classPathResult) {
    StringBuilder output = new StringBuilder();

    // Don't group AbstractMethodProblems by symbols because they do not fit in the
    // "... referenced by ..." format.
    ImmutableSet.Builder<AbstractMethodProblem> abstractMethodProblems = ImmutableSet.builder();
    ImmutableSet.Builder<LinkageProblem> problemsToGroupBySymbols = ImmutableSet.builder();
    for (LinkageProblem linkageProblem : linkageProblems) {
      if (linkageProblem instanceof AbstractMethodProblem) {
        abstractMethodProblems.add((AbstractMethodProblem) linkageProblem);
      } else {
        problemsToGroupBySymbols.add(linkageProblem);
      }
    }

    // Group by the symbols
    ImmutableListMultimap<Symbol, LinkageProblem> groupBySymbols =
        Multimaps.index(problemsToGroupBySymbols.build(), problem -> problem.getSymbol());

    groupBySymbols
        .asMap()
        .forEach(
            (symbol, problems) -> {
              // problems all have the same symbol problem
              LinkageProblem firstProblem = Iterables.getFirst(problems, null);
              int referenceCount = problems.size();
              output.append(firstProblem.formatSymbolProblemWithReferenceCount(referenceCount));
              ImmutableSet.Builder<LinkageProblemCause> causesBuilder = ImmutableSet.builder();
              problems.forEach(
                  problem -> {
                    ClassFile sourceClassFile = problem.getSourceClass();
                    output.append("    " + sourceClassFile.getBinaryName());
                    output.append(" (" + sourceClassFile.getClassPathEntry() + ")\n");

                    LinkageProblemCause cause = problem.getCause();
                    if (cause != null) {
                      causesBuilder.add(cause);
                    }
                  });
              ImmutableSet<LinkageProblemCause> causes = causesBuilder.build();
              if (!causes.isEmpty()) {
                output.append("  Cause:\n");
                for (LinkageProblemCause cause : causes) {
                  String causeWithIndent = cause.toString().replaceAll("\n", "\n    ");
                  output.append("    " + causeWithIndent + "\n");
                }
              }
            });

    for (AbstractMethodProblem abstractMethodProblem : abstractMethodProblems.build()) {
      output.append(abstractMethodProblem + "\n");
      output.append("  Cause:\n");
      LinkageProblemCause cause = abstractMethodProblem.getCause();
      String causeWithIndent = cause.toString().replaceAll("\n", "\n    ");
      output.append("    " + causeWithIndent + "\n");
    }

    if (classPathResult != null) {
      String dependencyPaths = dependencyPathsOfProblematicJars(classPathResult, linkageProblems);
      output.append(dependencyPaths);
    }

    return output.toString();
  }

    LinkageProblem(
      String symbolProblemMessage, ClassFile sourceClass, Symbol symbol, ClassFile targetClass);

}