com.google.inject.internal.MissingImplementationErrorHints Maven / Gradle / Ivy
The newest version!
package com.google.inject.internal;
import static com.google.common.collect.ImmutableList.toImmutableList;
import static java.lang.Math.min;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.primitives.Primitives;
import com.google.inject.Binding;
import com.google.inject.Injector;
import com.google.inject.Key;
import com.google.inject.TypeLiteral;
import com.google.inject.spi.BindingSourceRestriction;
import com.google.inject.spi.UntargettedBinding;
import java.util.ArrayList;
import java.util.Formatter;
import java.util.List;
import java.util.Map;
// TODO(b/165344346): Migrate to use suggest hints API.
/** Helper class to find hints for {@link MissingImplementationError}. */
final class MissingImplementationErrorHints {
private MissingImplementationErrorHints() {}
/** When a binding is not found, show at most this many bindings with the same type */
private static final int MAX_MATCHING_TYPES_REPORTED = 3;
/** When a binding is not found, show at most this many bindings that have some similarities */
private static final int MAX_RELATED_TYPES_REPORTED = 3;
/**
* If the key is unknown and it is one of these types, it generally means there is a missing
* annotation.
*/
private static final ImmutableSet> COMMON_AMBIGUOUS_TYPES =
ImmutableSet.>builder()
.add(Object.class)
.add(String.class)
.addAll(Primitives.allWrapperTypes())
.build();
static ImmutableList getSuggestions(Key key, Injector injector) {
ImmutableList.Builder suggestions = ImmutableList.builder();
TypeLiteral type = key.getTypeLiteral();
BindingSourceRestriction.getMissingImplementationSuggestion(GuiceInternal.GUICE_INTERNAL, key)
.ifPresent(suggestions::add);
// Keys which have similar strings as the desired key
List possibleMatches = new ArrayList<>();
ImmutableList> sameTypes =
injector.findBindingsByType(type).stream()
.filter(b -> !(b instanceof UntargettedBinding)) // These aren't valid matches
.collect(toImmutableList());
if (!sameTypes.isEmpty()) {
suggestions.add("\nDid you mean?");
int howMany = min(sameTypes.size(), MAX_MATCHING_TYPES_REPORTED);
for (int i = 0; i < howMany; ++i) {
Key> bindingKey = sameTypes.get(i).getKey();
// TODO: Look into a better way to prioritize suggestions. For example, possbily
// use levenshtein distance of the given annotation vs actual annotation.
suggestions.add(
Messages.format(
"\n * %s",
formatSuggestion(bindingKey, injector.getExistingBinding(bindingKey))));
}
int remaining = sameTypes.size() - MAX_MATCHING_TYPES_REPORTED;
if (remaining > 0) {
String plural = (remaining == 1) ? "" : "s";
suggestions.add(
Messages.format(
"\n * %d more binding%s with other annotations.", remaining, plural));
}
} else {
// For now, do a simple substring search for possibilities. This can help spot
// issues when there are generics being used (such as a wrapper class) and the
// user has forgotten they need to bind based on the wrapper, not the underlying
// class. In the future, consider doing a strict in-depth type search.
// TODO: Look into a better way to prioritize suggestions. For example, possbily
// use levenshtein distance of the type literal strings.
String want = type.toString();
Map, Binding>> bindingMap = injector.getAllBindings();
for (Key> bindingKey : bindingMap.keySet()) {
Binding> binding = bindingMap.get(bindingKey);
// Ignore untargeted bindings, those aren't valid matches.
if (binding instanceof UntargettedBinding) {
continue;
}
String have = bindingKey.getTypeLiteral().toString();
if (have.contains(want) || want.contains(have)) {
possibleMatches.add(formatSuggestion(bindingKey, bindingMap.get(bindingKey)));
// TODO: Consider a check that if there are more than some number of results,
// don't suggest any.
if (possibleMatches.size() > MAX_RELATED_TYPES_REPORTED) {
// Early exit if we have found more than we need.
break;
}
}
}
if (!possibleMatches.isEmpty() && (possibleMatches.size() <= MAX_RELATED_TYPES_REPORTED)) {
suggestions.add("\nDid you mean?");
for (String possibleMatch : possibleMatches) {
suggestions.add(Messages.format("\n * %s", possibleMatch));
}
}
}
// If where are no possibilities to suggest, then handle the case of missing
// annotations on simple types. This is usually a bad idea.
if (sameTypes.isEmpty()
&& possibleMatches.isEmpty()
&& key.getAnnotationType() == null
&& COMMON_AMBIGUOUS_TYPES.contains(key.getTypeLiteral().getRawType())) {
// We don't recommend using such simple types without annotations.
suggestions.add("\nThe key seems very generic, did you forget an annotation?");
}
return suggestions.build();
}
private static String formatSuggestion(Key> key, Binding> binding) {
Formatter fmt = new Formatter();
fmt.format("%s bound ", Messages.convert(key));
new SourceFormatter(binding.getSource(), fmt, /* omitPreposition= */ false).format();
return fmt.toString();
}
}