211 lines
8.2 KiB
Java
211 lines
8.2 KiB
Java
/*
|
|
* 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);
|
|
}
|
|
}
|