dagger.android.processor.AndroidMapKeyProcessingStep Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of dagger-android-processor Show documentation
Show all versions of dagger-android-processor Show documentation
A fast dependency injector for Android and Java.
/*
* Copyright (C) 2017 The Dagger Authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dagger.android.processor;
import static com.google.common.collect.Iterables.getOnlyElement;
import static dagger.android.processor.AndroidMapKeys.injectedTypeFromMapKey;
import static dagger.internal.codegen.xprocessing.XTypes.toStableString;
import androidx.room.compiler.processing.XAnnotation;
import androidx.room.compiler.processing.XElement;
import androidx.room.compiler.processing.XMethodElement;
import androidx.room.compiler.processing.XProcessingEnv;
import androidx.room.compiler.processing.XType;
import androidx.room.compiler.processing.XTypeElement;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Sets;
import com.squareup.javapoet.ClassName;
import dagger.internal.codegen.xprocessing.XElements;
import dagger.internal.codegen.xprocessing.XTypes;
import javax.tools.Diagnostic.Kind;
/** Validates the correctness of {@link dagger.MapKey}s used with {@code dagger.android}. */
final class AndroidMapKeyProcessingStep extends BaseProcessingStep {
private final XProcessingEnv processingEnv;
AndroidMapKeyProcessingStep(XProcessingEnv processingEnv) {
this.processingEnv = processingEnv;
}
@Override
public ImmutableSet annotationClassNames() {
return ImmutableSet.of(TypeNames.ANDROID_INJECTION_KEY, TypeNames.CLASS_KEY);
}
@Override
public void process(XElement element, ImmutableSet annotationNames) {
for (ClassName annotationName : annotationNames) {
validateMethod(annotationName, XElements.asMethod(element));
}
}
private void validateMethod(ClassName annotation, XMethodElement method) {
if (!Sets.union(
method.getAnnotationsAnnotatedWith(TypeNames.QUALIFIER),
method.getAnnotationsAnnotatedWith(TypeNames.QUALIFIER_JAVAX))
.isEmpty()) {
return;
}
XType returnType = method.getReturnType();
if (!factoryElement().getType().getRawType().isAssignableFrom(returnType.getRawType())) {
// if returnType is not related to AndroidInjector.Factory, ignore the method
return;
}
if (!Sets.union(
method.getAnnotationsAnnotatedWith(TypeNames.SCOPE),
method.getAnnotationsAnnotatedWith(TypeNames.SCOPE_JAVAX))
.isEmpty()) {
XAnnotation suppressedWarnings = method.getAnnotation(ClassName.get(SuppressWarnings.class));
if (suppressedWarnings == null
|| !ImmutableSet.copyOf(suppressedWarnings.getAsStringList("value"))
.contains("dagger.android.ScopedInjectorFactory")) {
XAnnotation mapKeyAnnotation =
getOnlyElement(method.getAnnotationsAnnotatedWith(TypeNames.MAP_KEY));
XTypeElement mapKeyValueElement =
processingEnv.requireTypeElement(injectedTypeFromMapKey(mapKeyAnnotation).get());
processingEnv
.getMessager()
.printMessage(
Kind.ERROR,
String.format(
"%s bindings should not be scoped. Scoping this method may leak instances of"
+ " %s.",
TypeNames.ANDROID_INJECTOR_FACTORY.canonicalName(),
mapKeyValueElement.getQualifiedName()),
method);
}
}
validateReturnType(method);
// @Binds methods should only have one parameter, but we can't guarantee the order of Processors
// in javac, so do a basic check for valid form
if (method.hasAnnotation(TypeNames.BINDS) && method.getParameters().size() == 1) {
validateMapKeyMatchesBindsParameter(annotation, method);
}
}
/** Report an error if the method's return type is not {@code AndroidInjector.Factory>}. */
private void validateReturnType(XMethodElement method) {
XType returnType = method.getReturnType();
XType requiredReturnType = injectorFactoryOf(processingEnv.getWildcardType(null, null));
// TODO(b/311460276) use XType.isSameType when the bug is fixed.
if (!returnType.getTypeName().equals(requiredReturnType.getTypeName())) {
processingEnv
.getMessager()
.printMessage(
Kind.ERROR,
String.format(
"%s should bind %s, not %s. See https://dagger.dev/android",
method, toStableString(requiredReturnType), toStableString(returnType)),
method);
}
}
/**
* A valid @Binds method could bind an {@code AndroidInjector.Factory} for one type, while giving
* it a map key of a different type. The return type and parameter type would pass typical @Binds
* validation, but the map lookup in {@code DispatchingAndroidInjector} would retrieve the wrong
* injector factory.
*
* {@code
* {@literal @Binds}
* {@literal @IntoMap}
* {@literal @ClassKey(GreenActivity.class)}
* abstract AndroidInjector.Factory> bindBlueActivity(
* BlueActivityComponent.Builder builder);
* }
*/
private void validateMapKeyMatchesBindsParameter(
ClassName annotationName, XMethodElement method) {
XType parameterType = getOnlyElement(method.getParameters()).getType();
XAnnotation annotation = method.getAnnotation(annotationName);
XType mapKeyType =
processingEnv.requireTypeElement(injectedTypeFromMapKey(annotation).get()).getType();
if (!XTypes.isAssignableTo(parameterType, injectorFactoryOf(mapKeyType))) {
processingEnv
.getMessager()
.printMessage(
Kind.ERROR,
String.format(
"%s does not implement AndroidInjector<%s>",
toStableString(parameterType), toStableString(mapKeyType)),
method,
annotation);
}
}
/** Returns a {@link XType} for {@code AndroidInjector.Factory}. */
private XType injectorFactoryOf(XType implementationType) {
return processingEnv.getDeclaredType(factoryElement(), implementationType);
}
private XTypeElement factoryElement() {
return processingEnv.requireTypeElement(TypeNames.ANDROID_INJECTOR_FACTORY.canonicalName());
}
}