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