• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright 2013 Google LLC
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 package com.google.auto.factory.processor;
17 
18 import static com.google.auto.common.MoreTypes.asElement;
19 import static com.google.auto.common.MoreTypes.asTypeElement;
20 import static javax.lang.model.element.ElementKind.ANNOTATION_TYPE;
21 import static javax.lang.model.type.TypeKind.DECLARED;
22 import static javax.lang.model.type.TypeKind.ERROR;
23 import static javax.lang.model.util.ElementFilter.methodsIn;
24 
25 import com.google.auto.common.MoreTypes;
26 import com.google.auto.factory.AutoFactory;
27 import com.google.auto.factory.AutoFactory.AnnotationsToApply;
28 import com.google.auto.factory.Provided;
29 import com.google.auto.service.AutoService;
30 import com.google.common.base.Throwables;
31 import com.google.common.collect.ImmutableList;
32 import com.google.common.collect.ImmutableListMultimap;
33 import com.google.common.collect.ImmutableSet;
34 import com.google.common.collect.ImmutableSetMultimap;
35 import com.google.common.collect.ImmutableSortedSet;
36 import com.google.common.collect.Iterables;
37 import java.io.IOException;
38 import java.util.Arrays;
39 import java.util.Comparator;
40 import java.util.HashSet;
41 import java.util.List;
42 import java.util.Optional;
43 import java.util.Set;
44 import javax.annotation.processing.AbstractProcessor;
45 import javax.annotation.processing.Messager;
46 import javax.annotation.processing.ProcessingEnvironment;
47 import javax.annotation.processing.Processor;
48 import javax.annotation.processing.RoundEnvironment;
49 import javax.lang.model.SourceVersion;
50 import javax.lang.model.element.AnnotationMirror;
51 import javax.lang.model.element.Element;
52 import javax.lang.model.element.ExecutableElement;
53 import javax.lang.model.element.Modifier;
54 import javax.lang.model.element.TypeElement;
55 import javax.lang.model.type.ExecutableType;
56 import javax.lang.model.type.TypeMirror;
57 import javax.lang.model.util.ElementFilter;
58 import javax.lang.model.util.Elements;
59 import javax.lang.model.util.Types;
60 import javax.tools.Diagnostic.Kind;
61 import net.ltgt.gradle.incap.IncrementalAnnotationProcessor;
62 import net.ltgt.gradle.incap.IncrementalAnnotationProcessorType;
63 
64 /**
65  * The annotation processor that generates factories for {@link AutoFactory} annotations.
66  *
67  * @author Gregory Kick
68  */
69 @IncrementalAnnotationProcessor(IncrementalAnnotationProcessorType.ISOLATING)
70 @AutoService(Processor.class)
71 public final class AutoFactoryProcessor extends AbstractProcessor {
72   private FactoryDescriptorGenerator factoryDescriptorGenerator;
73   private AutoFactoryDeclaration.Factory declarationFactory;
74   private ProvidedChecker providedChecker;
75   private Messager messager;
76   private Elements elements;
77   private Types types;
78 
79   @Override
init(ProcessingEnvironment processingEnv)80   public synchronized void init(ProcessingEnvironment processingEnv) {
81     super.init(processingEnv);
82     elements = processingEnv.getElementUtils();
83     types = processingEnv.getTypeUtils();
84     messager = processingEnv.getMessager();
85     providedChecker = new ProvidedChecker(messager);
86     declarationFactory = new AutoFactoryDeclaration.Factory(elements, messager);
87     factoryDescriptorGenerator =
88         new FactoryDescriptorGenerator(messager, types, declarationFactory);
89   }
90 
91   @Override
process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv)92   public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
93     try {
94       doProcess(roundEnv);
95     } catch (Throwable e) {
96       messager.printMessage(
97           Kind.ERROR,
98           "Failed to process @AutoFactory annotations:\n" + Throwables.getStackTraceAsString(e));
99     }
100     return false;
101   }
102 
doProcess(RoundEnvironment roundEnv)103   private void doProcess(RoundEnvironment roundEnv) {
104     for (Element element : roundEnv.getElementsAnnotatedWith(Provided.class)) {
105       providedChecker.checkProvidedParameter(element);
106     }
107 
108     for (Element element :
109         roundEnv.getElementsAnnotatedWith(AnnotationsToApply.class)) {
110       checkAnnotationsToApply(element);
111     }
112 
113     ImmutableListMultimap.Builder<PackageAndClass, FactoryMethodDescriptor> indexedMethodsBuilder =
114         ImmutableListMultimap.builder();
115     ImmutableSetMultimap.Builder<PackageAndClass, ImplementationMethodDescriptor>
116         implementationMethodDescriptorsBuilder = ImmutableSetMultimap.builder();
117     // Iterate over the classes and constructors that are annotated with @AutoFactory.
118     for (Element element : roundEnv.getElementsAnnotatedWith(AutoFactory.class)) {
119       Optional<AutoFactoryDeclaration> declaration = declarationFactory.createIfValid(element);
120       if (declaration.isPresent()) {
121         PackageAndClass factoryName = declaration.get().getFactoryName();
122         TypeElement extendingType = declaration.get().extendingType();
123         implementationMethodDescriptorsBuilder.putAll(
124             factoryName, implementationMethods(extendingType, element));
125         for (TypeElement implementingType : declaration.get().implementingTypes()) {
126           implementationMethodDescriptorsBuilder.putAll(
127               factoryName, implementationMethods(implementingType, element));
128         }
129       }
130 
131       ImmutableSet<FactoryMethodDescriptor> descriptors =
132           factoryDescriptorGenerator.generateDescriptor(element);
133       for (FactoryMethodDescriptor descriptor : descriptors) {
134         indexedMethodsBuilder.put(descriptor.factoryName(), descriptor);
135       }
136     }
137 
138     ImmutableSetMultimap<PackageAndClass, ImplementationMethodDescriptor>
139         implementationMethodDescriptors = implementationMethodDescriptorsBuilder.build();
140     ImmutableListMultimap<PackageAndClass, FactoryMethodDescriptor> indexedMethods =
141         indexedMethodsBuilder.build();
142     ImmutableSetMultimap<String, PackageAndClass> factoriesBeingCreated =
143         simpleNamesToNames(indexedMethods.keySet());
144     FactoryWriter factoryWriter = new FactoryWriter(processingEnv, factoriesBeingCreated);
145 
146     indexedMethods
147         .asMap()
148         .forEach(
149             (factoryName, methodDescriptors) -> {
150               if (methodDescriptors.isEmpty()) {
151                 // This shouldn't happen, but check anyway to avoid an exception for
152                 // methodDescriptors.iterator().next() below.
153                 return;
154               }
155               // Sort to ensure output is deterministic.
156               ImmutableSortedSet.Builder<AnnotationMirror> annotationsBuilder =
157                   ImmutableSortedSet.orderedBy(ANNOTATION_COMPARATOR);
158               // The sets of classes that are mentioned in the `extending` and `implementing`
159               // elements, respectively, of the @AutoFactory annotations for this factory.
160               ImmutableSet.Builder<TypeMirror> extending = newTypeSetBuilder();
161               ImmutableSortedSet.Builder<TypeMirror> implementing = newTypeSetBuilder();
162               boolean publicType = false;
163               Set<Boolean> allowSubclassesSet = new HashSet<>();
164               boolean skipCreation = false;
165               for (FactoryMethodDescriptor methodDescriptor : methodDescriptors) {
166                 annotationsBuilder.addAll(methodDescriptor.declaration().annotations());
167                 extending.add(methodDescriptor.declaration().extendingType().asType());
168                 for (TypeElement implementingType :
169                     methodDescriptor.declaration().implementingTypes()) {
170                   implementing.add(implementingType.asType());
171                 }
172                 publicType |= methodDescriptor.publicMethod();
173                 allowSubclassesSet.add(methodDescriptor.declaration().allowSubclasses());
174                 if (allowSubclassesSet.size() > 1) {
175                   skipCreation = true;
176                   messager.printMessage(
177                       Kind.ERROR,
178                       "Cannot mix allowSubclasses=true and allowSubclasses=false in one factory.",
179                       methodDescriptor.declaration().target(),
180                       methodDescriptor.declaration().mirror(),
181                       methodDescriptor.declaration().valuesMap().get("allowSubclasses"));
182                 }
183               }
184               // The set can't be empty because we eliminated methodDescriptors.isEmpty() above.
185               boolean allowSubclasses = allowSubclassesSet.iterator().next();
186               if (!skipCreation) {
187                 try {
188                   factoryWriter.writeFactory(
189                       FactoryDescriptor.create(
190                           factoryName,
191                           annotationsBuilder.build(),
192                           Iterables.getOnlyElement(extending.build()),
193                           implementing.build(),
194                           publicType,
195                           ImmutableSet.copyOf(methodDescriptors),
196                           implementationMethodDescriptors.get(factoryName),
197                           allowSubclasses));
198                 } catch (IOException e) {
199                   messager.printMessage(Kind.ERROR, "failed: " + e);
200                 }
201               }
202             });
203   }
204 
205   private static final Comparator<AnnotationMirror> ANNOTATION_COMPARATOR =
206       Comparator.comparing(mirror -> mirror.getAnnotationType().toString());
207 
implementationMethods( TypeElement supertype, Element autoFactoryElement)208   private ImmutableSet<ImplementationMethodDescriptor> implementationMethods(
209       TypeElement supertype, Element autoFactoryElement) {
210     ImmutableSet.Builder<ImplementationMethodDescriptor> implementationMethodsBuilder =
211         ImmutableSet.builder();
212     for (ExecutableElement implementationMethod :
213         ElementFilter.methodsIn(elements.getAllMembers(supertype))) {
214       if (implementationMethod.getModifiers().contains(Modifier.ABSTRACT)) {
215         ExecutableType methodType =
216             Elements2.getExecutableElementAsMemberOf(types, implementationMethod, supertype);
217         ImmutableSet<Parameter> passedParameters =
218             Parameter.forParameterList(
219                 implementationMethod.getParameters(), methodType.getParameterTypes(), types);
220         implementationMethodsBuilder.add(
221             ImplementationMethodDescriptor.builder()
222                 .name(implementationMethod.getSimpleName().toString())
223                 .returnType(getAnnotatedType(autoFactoryElement))
224                 .publicMethod()
225                 .passedParameters(passedParameters)
226                 .isVarArgs(implementationMethod.isVarArgs())
227                 .exceptions(implementationMethod.getThrownTypes())
228                 .build());
229       }
230     }
231     return implementationMethodsBuilder.build();
232   }
233 
getAnnotatedType(Element element)234   private TypeMirror getAnnotatedType(Element element) {
235     List<TypeElement> types = ImmutableList.of();
236     while (types.isEmpty()) {
237       types = ElementFilter.typesIn(Arrays.asList(element));
238       element = element.getEnclosingElement();
239     }
240     return Iterables.getOnlyElement(types).asType();
241   }
242 
simpleNamesToNames( ImmutableSet<PackageAndClass> names)243   private static ImmutableSetMultimap<String, PackageAndClass> simpleNamesToNames(
244       ImmutableSet<PackageAndClass> names) {
245     // .collect(toImmutableSetMultimap(...)) would make this much simpler but ran into problems in
246     // Google's internal build system because of multiple Guava versions.
247     ImmutableSetMultimap.Builder<String, PackageAndClass> builder = ImmutableSetMultimap.builder();
248     for (PackageAndClass name : names) {
249       builder.put(name.className(), name);
250     }
251     return builder.build();
252   }
253 
newTypeSetBuilder()254   private static ImmutableSortedSet.Builder<TypeMirror> newTypeSetBuilder() {
255     return ImmutableSortedSet.orderedBy(
256         Comparator.comparing(t -> MoreTypes.asTypeElement(t).getQualifiedName().toString()));
257   }
258 
259   /** Checks that {@link AnnotationsToApply} is used correctly. */
checkAnnotationsToApply(Element annotation)260   private void checkAnnotationsToApply(Element annotation) {
261     if (!annotation.getKind().equals(ANNOTATION_TYPE)) {
262       // Should not be possible because of @Target.
263       messager.printMessage(
264           Kind.ERROR,
265           "@"
266               + AnnotationsToApply.class.getSimpleName()
267               + " must be applied to an annotation type declaration.",
268           annotation);
269     }
270     Set<TypeElement> seenAnnotations = new HashSet<>();
271     for (ExecutableElement annotationMember : methodsIn(annotation.getEnclosedElements())) {
272       TypeMirror memberType = annotationMember.getReturnType();
273       boolean isAnnotation = memberType.getKind().equals(DECLARED) && asElement(memberType).getKind().equals(ANNOTATION_TYPE);
274       if (!isAnnotation && !memberType.getKind().equals(ERROR)) {
275         messager.printMessage(
276             Kind.ERROR,
277             "Members of an @"
278                 + AnnotationsToApply.class.getSimpleName()
279                 + " annotation must themselves be annotations; "
280                 + annotationMember.getSimpleName()
281                 + " has type "
282                 + memberType,
283             annotationMember);
284       } else {
285         TypeElement annotationElement = asTypeElement(memberType);
286         if (!seenAnnotations.add(annotationElement)) {
287           messager.printMessage(
288               Kind.ERROR, "More than one @" + annotationElement + " in " + annotation, annotation);
289         }
290       }
291     }
292   }
293 
294   @Override
getSupportedAnnotationTypes()295   public ImmutableSet<String> getSupportedAnnotationTypes() {
296     return ImmutableSet.of(
297         AutoFactory.class.getCanonicalName(),
298         Provided.class.getCanonicalName(),
299         AnnotationsToApply.class.getCanonicalName());
300   }
301 
302   @Override
getSupportedSourceVersion()303   public SourceVersion getSupportedSourceVersion() {
304     return SourceVersion.latestSupported();
305   }
306 }
307