All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.google.inject.internal.DuplicateElementError Maven / Gradle / Ivy

There is a newer version: 7.0.0
Show newest version
package com.google.inject.internal;

import com.google.common.collect.ImmutableMultimap;
import com.google.inject.Binding;
import com.google.inject.Key;
import com.google.inject.spi.Dependency;
import com.google.inject.spi.ErrorDetail;
import java.util.Collection;
import java.util.Formatter;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;

/**
 * Error reported by Guice when duplicate elements are found in a {@link Multibinder} that does not
 * permit duplicates.
 */
final class DuplicateElementError extends InternalErrorDetail> {
  private final Key> setKey;
  private final ImmutableMultimap> elements;

  DuplicateElementError(
      Key> setKey, List> bindings, T[] values, List sources) {
    this(setKey, indexElements(bindings, values), sources);
  }

  private DuplicateElementError(
      Key> setKey, ImmutableMultimap> elements, List sources) {
    super(
        ErrorId.DUPLICATE_ELEMENT,
        String.format("Duplicate elements found in Multibinder %s.", Messages.convert(setKey)),
        sources,
        null);
    this.setKey = setKey;
    this.elements = elements;
  }

  @Override
  protected void formatDetail(List> others, Formatter formatter) {
    formatter.format("%n%s%n", Messages.bold("Duplicates:"));
    int duplicateIndex = 1;
    for (Map.Entry>> entry : elements.asMap().entrySet()) {
      formatter.format("%-2s: ", duplicateIndex++);
      if (entry.getValue().size() > 1) {
        Set valuesAsString =
            entry.getValue().stream()
                .map(element -> element.value.toString())
                .collect(Collectors.toSet());
        if (valuesAsString.size() == 1) {
          // String representation of the duplicates elements are the same, so only print out one.
          formatter.format("Element: %s%n", Messages.redBold(valuesAsString.iterator().next()));
          formatter.format("    Bound at:%n");
          int index = 1;
          for (Element element : entry.getValue()) {
            formatter.format("    %-2s: ", index++);
            formatElement(element, formatter);
          }
        } else {
          // Print out all elements as string when there are different string representations of the
          // elements. To keep the logic simple, same strings are not grouped together unless all
          // elements have the same string represnetation. This means some strings may be printed
          // out multiple times.
          // There is no indentation for the first duplicate element.
          boolean indent = false;
          for (Element element : entry.getValue()) {
            if (indent) {
              formatter.format("    ");
            } else {
              indent = true;
            }
            formatter.format("Element: %s%n", Messages.redBold(element.value.toString()));
            formatter.format("    Bound at: ");
            formatElement(element, formatter);
          }
        }
      }
    }
    formatter.format("%n%s%n", Messages.bold("Multibinder declared at:"));
    // Multibinder source includes the key of the set. Filter it out since it's not useful in the
    // printed error stack.
    List filteredSource =
        getSources().stream()
            .filter(
                source -> {
                  if (source instanceof Dependency) {
                    return !((Dependency) source).getKey().equals(setKey);
                  }
                  return true;
                })
            .collect(Collectors.toList());
    ErrorFormatter.formatSources(filteredSource, formatter);
  }

  private void formatElement(Element element, Formatter formatter) {
    Object source = element.binding.getSource();
    new SourceFormatter(
            source,
            formatter,
            /** omitPreposition= */
            true)
        .format();
  }

  @Override
  public DuplicateElementError withSources(List newSources) {
    return new DuplicateElementError<>(setKey, elements, newSources);
  }

  static  ImmutableMultimap> indexElements(List> bindings, T[] values) {
    ImmutableMultimap.Builder> map = ImmutableMultimap.builder();
    for (int i = 0; i < values.length; i++) {
      map.put(values[i], new Element(values[i], bindings.get(i)));
    }
    return map.build();
  }

  static class Element {
    T value;
    Binding binding;

    Element(T value, Binding binding) {
      this.value = value;
      this.binding = binding;
    }
  }
}