1 /*
2  * Copyright (C) 2020 The Dagger Authors.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package dagger.hilt.android.processor.internal.bindvalue;
18 
19 import static net.ltgt.gradle.incap.IncrementalAnnotationProcessorType.ISOLATING;
20 
21 import com.google.auto.common.MoreElements;
22 import com.google.auto.service.AutoService;
23 import com.google.common.collect.ArrayListMultimap;
24 import com.google.common.collect.ImmutableList;
25 import com.google.common.collect.ImmutableSet;
26 import com.google.common.collect.Multimap;
27 import com.squareup.javapoet.AnnotationSpec;
28 import com.squareup.javapoet.ClassName;
29 import com.squareup.javapoet.TypeName;
30 import dagger.hilt.processor.internal.BaseProcessor;
31 import dagger.hilt.processor.internal.ClassNames;
32 import dagger.hilt.processor.internal.ProcessorErrors;
33 import dagger.hilt.processor.internal.Processors;
34 import dagger.internal.codegen.extension.DaggerStreams;
35 import java.util.Collection;
36 import java.util.Map;
37 import javax.annotation.processing.Processor;
38 import javax.annotation.processing.RoundEnvironment;
39 import javax.lang.model.element.AnnotationMirror;
40 import javax.lang.model.element.Element;
41 import javax.lang.model.element.ElementKind;
42 import javax.lang.model.element.TypeElement;
43 import net.ltgt.gradle.incap.IncrementalAnnotationProcessor;
44 
45 /** Provides a test's @BindValue fields to the SINGLETON component. */
46 @IncrementalAnnotationProcessor(ISOLATING)
47 @AutoService(Processor.class)
48 public final class BindValueProcessor extends BaseProcessor {
49 
50   private static final ImmutableSet<ClassName> SUPPORTED_ANNOTATIONS =
51       ImmutableSet.<ClassName>builder()
52           .addAll(BindValueMetadata.BIND_VALUE_ANNOTATIONS)
53           .addAll(BindValueMetadata.BIND_VALUE_INTO_SET_ANNOTATIONS)
54           .addAll(BindValueMetadata.BIND_ELEMENTS_INTO_SET_ANNOTATIONS)
55           .addAll(BindValueMetadata.BIND_VALUE_INTO_MAP_ANNOTATIONS)
56           .build();
57 
58   private final Multimap<TypeElement, Element> testRootMap = ArrayListMultimap.create();
59 
60   @Override
getSupportedAnnotationTypes()61   public ImmutableSet<String> getSupportedAnnotationTypes() {
62     return SUPPORTED_ANNOTATIONS.stream()
63         .map(TypeName::toString)
64         .collect(DaggerStreams.toImmutableSet());
65   }
66 
67   @Override
preRoundProcess(RoundEnvironment roundEnv)68   protected void preRoundProcess(RoundEnvironment roundEnv) {
69     testRootMap.clear();
70   }
71 
72   @Override
processEach(TypeElement annotation, Element element)73   public void processEach(TypeElement annotation, Element element) throws Exception {
74     ClassName annotationClassName = ClassName.get(annotation);
75     Element enclosingElement = element.getEnclosingElement();
76     // Restrict BindValue to the direct test class (e.g. not allowed in a base test class) because
77     // otherwise generated BindValue modules from the base class will not associate with the
78     // correct test class. This would make the modules apply globally which would be a weird
79     // difference since just moving a declaration to the parent would change whether the module is
80     // limited to the test that declares it to global.
81     ProcessorErrors.checkState(
82         enclosingElement.getKind() == ElementKind.CLASS
83             && (Processors.hasAnnotation(enclosingElement, ClassNames.HILT_ANDROID_TEST)
84             ),
85         enclosingElement,
86         "@%s can only be used within a class annotated with "
87             + "@HiltAndroidTest. Found: %s",
88         annotationClassName.simpleName(),
89         enclosingElement);
90 
91     testRootMap.put(MoreElements.asType(enclosingElement), element);
92   }
93 
94   @Override
postRoundProcess(RoundEnvironment roundEnvironment)95   public void postRoundProcess(RoundEnvironment roundEnvironment) throws Exception {
96     // Generate a module for each testing class with a @BindValue field.
97     for (Map.Entry<TypeElement, Collection<Element>> e : testRootMap.asMap().entrySet()) {
98       BindValueMetadata metadata = BindValueMetadata.create(e.getKey(), e.getValue());
99       new BindValueGenerator(getProcessingEnv(), metadata).generate();
100     }
101   }
102 
getBindValueAnnotations(Element element)103   static ImmutableList<ClassName> getBindValueAnnotations(Element element) {
104     ImmutableList.Builder<ClassName> builder = ImmutableList.builder();
105     for (AnnotationMirror annotation : element.getAnnotationMirrors()) {
106       TypeName tn = AnnotationSpec.get(annotation).type;
107       if (SUPPORTED_ANNOTATIONS.contains(tn)) {
108         builder.add((ClassName) tn); // the cast is checked by .contains()
109       }
110     }
111     return builder.build();
112   }
113 }
114