blob: 06b994ff77fc54f60e6309660bab3e19ddce7737 [file] [log] [blame]
/*
* Copyright (C) 2018 The Android Open Source Project
*
* 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 com.android.dialer.rootcomponentgenerator;
import static com.google.auto.common.AnnotationMirrors.getAnnotationValue;
import static com.google.auto.common.MoreElements.getAnnotationMirror;
import static com.google.auto.common.MoreElements.isAnnotationPresent;
import static javax.tools.Diagnostic.Kind.ERROR;
import com.android.dialer.inject.DialerRootComponent;
import com.android.dialer.inject.DialerVariant;
import com.android.dialer.inject.IncludeInDialerRoot;
import com.android.dialer.inject.InstallIn;
import com.android.dialer.inject.RootComponentGeneratorMetadata;
import com.google.auto.common.BasicAnnotationProcessor.ProcessingStep;
import com.google.auto.common.MoreElements;
import com.google.common.base.Optional;
import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.ListMultimap;
import com.google.common.collect.SetMultimap;
import com.squareup.javapoet.AnnotationSpec;
import com.squareup.javapoet.ClassName;
import com.squareup.javapoet.MethodSpec;
import com.squareup.javapoet.TypeSpec;
import dagger.Component;
import java.lang.annotation.Annotation;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import javax.annotation.processing.ProcessingEnvironment;
import javax.inject.Singleton;
import javax.lang.model.element.AnnotationMirror;
import javax.lang.model.element.Element;
import javax.lang.model.element.Modifier;
import javax.lang.model.element.PackageElement;
import javax.lang.model.element.TypeElement;
import javax.lang.model.type.TypeMirror;
/**
* Generates root component for a java type annotated with {@link DialerRootComponent}.
*
* <p>If users use {@link GenerateTestDaggerApp} along with RootComponentGenerator, there's an
* optional method that they can use to inject instance in the test.
*
* <p>Example:
*
* <p>
*
* <pre>
* <code>
* @Inject SomeThing someThing;
* @Before
* public void setUp() {
* ...
* TestApplication application = (TestApplication) RuntimeEnvironment.application;
* TestComponent component = (TestComponent) application.component();
* component.inject(this);
* ...
* }
* </code>
* </pre>
*/
final class RootComponentGeneratingStep implements ProcessingStep {
private final ProcessingEnvironment processingEnv;
public RootComponentGeneratingStep(ProcessingEnvironment processingEnv) {
this.processingEnv = processingEnv;
}
@Override
public Set<? extends Class<? extends Annotation>> annotations() {
return ImmutableSet.of(DialerRootComponent.class, InstallIn.class, IncludeInDialerRoot.class);
}
@Override
public Set<? extends Element> process(
SetMultimap<Class<? extends Annotation>, Element> elementsByAnnotation) {
for (Element element : elementsByAnnotation.get(DialerRootComponent.class)) {
// defer root components to the next round in case where the current build target contains
// elements annotated with @InstallIn. Annotation processor cannot detect metadata files
// generated in the same round and the metadata is accessible in the next round.
if (shouldDeferRootComponent(elementsByAnnotation)) {
return elementsByAnnotation.get(DialerRootComponent.class);
} else {
generateRootComponent(MoreElements.asType(element));
}
}
return Collections.emptySet();
}
private boolean shouldDeferRootComponent(
SetMultimap<Class<? extends Annotation>, Element> elementsByAnnotation) {
return elementsByAnnotation.containsKey(InstallIn.class)
|| elementsByAnnotation.containsKey(IncludeInDialerRoot.class);
}
/**
* Generates a root component.
*
* @param rootElement the annotated type with {@link DialerRootComponent} used in annotation
* processor.
*/
private void generateRootComponent(TypeElement rootElement) {
DialerRootComponent dialerRootComponent = rootElement.getAnnotation(DialerRootComponent.class);
ListMultimap<DialerVariant, TypeElement> componentModuleMap = generateComponentModuleMap();
List<TypeElement> componentList = generateComponentList();
DialerVariant dialerVariant = dialerRootComponent.variant();
TypeSpec.Builder rootComponentClassBuilder =
TypeSpec.interfaceBuilder(dialerVariant.toString())
.addModifiers(Modifier.PUBLIC)
.addAnnotation(Singleton.class);
for (TypeElement componentWithSuperInterface : componentList) {
rootComponentClassBuilder.addSuperinterface(ClassName.get(componentWithSuperInterface));
}
AnnotationSpec.Builder componentAnnotation = AnnotationSpec.builder(Component.class);
for (TypeElement annotatedElement : componentModuleMap.get(dialerVariant)) {
componentAnnotation.addMember("modules", "$T.class", annotatedElement.asType());
}
rootComponentClassBuilder.addAnnotation(componentAnnotation.build());
AnnotationMirror dialerRootComponentMirror =
getAnnotationMirror(rootElement, DialerRootComponent.class).get();
TypeMirror annotatedTestClass =
(TypeMirror) getAnnotationValue(dialerRootComponentMirror, "injectClass").getValue();
rootComponentClassBuilder.addMethod(generateInjectMethod(annotatedTestClass));
TypeSpec rootComponentClass = rootComponentClassBuilder.build();
RootComponentUtils.writeJavaFile(
processingEnv, ClassName.get(rootElement).packageName(), rootComponentClass);
}
private List<TypeElement> generateComponentList() {
List<TypeElement> list = new ArrayList<>();
extractInfoFromMetadata(IncludeInDialerRoot.class, list::add);
return list;
}
private ListMultimap<DialerVariant, TypeElement> generateComponentModuleMap() {
ListMultimap<DialerVariant, TypeElement> map = ArrayListMultimap.create();
extractInfoFromMetadata(
InstallIn.class,
(annotatedElement) -> {
for (DialerVariant rootComponentName :
annotatedElement.getAnnotation(InstallIn.class).variants()) {
map.put(rootComponentName, annotatedElement);
}
});
return map;
}
private void extractInfoFromMetadata(
Class<? extends Annotation> annotation, MetadataProcessor metadataProcessor) {
PackageElement cachePackage =
processingEnv.getElementUtils().getPackageElement(RootComponentUtils.METADATA_PACKAGE_NAME);
if (cachePackage == null) {
processingEnv
.getMessager()
.printMessage(
ERROR,
"Metadata haven't been generated! do you forget to add modules "
+ "or components in dependency of dialer root?");
return;
}
for (Element element : cachePackage.getEnclosedElements()) {
Optional<AnnotationMirror> metadataAnnotation =
getAnnotationMirror(element, RootComponentGeneratorMetadata.class);
if (isAnnotationPresent(element, RootComponentGeneratorMetadata.class)
&& getAnnotationValue(metadataAnnotation.get(), "tag")
.getValue()
.equals(annotation.getSimpleName())) {
TypeMirror annotatedClass =
(TypeMirror) getAnnotationValue(metadataAnnotation.get(), "annotatedClass").getValue();
TypeElement annotatedElement =
processingEnv.getElementUtils().getTypeElement(annotatedClass.toString());
metadataProcessor.process(annotatedElement);
}
}
}
private MethodSpec generateInjectMethod(TypeMirror testClassTypeMirror) {
return MethodSpec.methodBuilder("inject")
.addModifiers(Modifier.PUBLIC, Modifier.ABSTRACT)
.returns(void.class)
.addParameter(ClassName.get(testClassTypeMirror), "clazz")
.build();
}
private interface MetadataProcessor {
void process(TypeElement typeElement);
}
}