blob: cf3d5ebd23872db1bf654e65a66472e3f398aa86 [file] [log] [blame]
/*
* Copyright 2013 Google LLC
*
* 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.google.auto.factory.processor;
import com.google.auto.common.MoreTypes;
import com.google.auto.factory.AutoFactory;
import com.google.auto.factory.Provided;
import com.google.auto.service.AutoService;
import com.google.common.base.Optional;
import com.google.common.base.Throwables;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableListMultimap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.ImmutableSetMultimap;
import com.google.common.collect.ImmutableSortedSet;
import com.google.common.collect.Iterables;
import com.google.googlejavaformat.java.filer.FormattingFiler;
import java.io.IOException;
import java.util.Arrays;
import java.util.Collection;
import java.util.Comparator;
import java.util.List;
import java.util.Map.Entry;
import java.util.Set;
import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.Messager;
import javax.annotation.processing.ProcessingEnvironment;
import javax.annotation.processing.Processor;
import javax.annotation.processing.RoundEnvironment;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.Element;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.Modifier;
import javax.lang.model.element.TypeElement;
import javax.lang.model.type.ExecutableType;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.util.ElementFilter;
import javax.lang.model.util.Elements;
import javax.lang.model.util.Types;
import javax.tools.Diagnostic.Kind;
import net.ltgt.gradle.incap.IncrementalAnnotationProcessor;
import net.ltgt.gradle.incap.IncrementalAnnotationProcessorType;
/**
* The annotation processor that generates factories for {@link AutoFactory} annotations.
*
* @author Gregory Kick
*/
@IncrementalAnnotationProcessor(IncrementalAnnotationProcessorType.ISOLATING)
@AutoService(Processor.class)
public final class AutoFactoryProcessor extends AbstractProcessor {
private FactoryDescriptorGenerator factoryDescriptorGenerator;
private AutoFactoryDeclaration.Factory declarationFactory;
private ProvidedChecker providedChecker;
private Messager messager;
private Elements elements;
private Types types;
private FactoryWriter factoryWriter;
@Override
public synchronized void init(ProcessingEnvironment processingEnv) {
super.init(processingEnv);
elements = processingEnv.getElementUtils();
types = processingEnv.getTypeUtils();
messager = processingEnv.getMessager();
factoryWriter =
new FactoryWriter(
new FormattingFiler(processingEnv.getFiler()),
elements,
processingEnv.getSourceVersion());
providedChecker = new ProvidedChecker(messager);
declarationFactory = new AutoFactoryDeclaration.Factory(elements, messager);
factoryDescriptorGenerator =
new FactoryDescriptorGenerator(messager, types, declarationFactory);
}
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
try {
doProcess(roundEnv);
} catch (Throwable e) {
messager.printMessage(Kind.ERROR, "Failed to process @AutoFactory annotations:\n"
+ Throwables.getStackTraceAsString(e));
}
return false;
}
private void doProcess(RoundEnvironment roundEnv) {
for (Element element : roundEnv.getElementsAnnotatedWith(Provided.class)) {
providedChecker.checkProvidedParameter(element);
}
ImmutableListMultimap.Builder<String, FactoryMethodDescriptor> indexedMethods =
ImmutableListMultimap.builder();
ImmutableSetMultimap.Builder<String, ImplementationMethodDescriptor>
implementationMethodDescriptorsBuilder = ImmutableSetMultimap.builder();
for (Element element : roundEnv.getElementsAnnotatedWith(AutoFactory.class)) {
Optional<AutoFactoryDeclaration> declaration = declarationFactory.createIfValid(element);
if (declaration.isPresent()) {
String factoryName = declaration.get().getFactoryName();
TypeElement extendingType = declaration.get().extendingType();
implementationMethodDescriptorsBuilder.putAll(
factoryName, implementationMethods(extendingType, element));
for (TypeElement implementingType : declaration.get().implementingTypes()) {
implementationMethodDescriptorsBuilder.putAll(
factoryName, implementationMethods(implementingType, element));
}
}
ImmutableSet<FactoryMethodDescriptor> descriptors =
factoryDescriptorGenerator.generateDescriptor(element);
for (FactoryMethodDescriptor descriptor : descriptors) {
indexedMethods.put(descriptor.factoryName(), descriptor);
}
}
ImmutableSetMultimap<String, ImplementationMethodDescriptor>
implementationMethodDescriptors = implementationMethodDescriptorsBuilder.build();
for (Entry<String, Collection<FactoryMethodDescriptor>> entry
: indexedMethods.build().asMap().entrySet()) {
ImmutableSet.Builder<TypeMirror> extending = ImmutableSet.builder();
ImmutableSortedSet.Builder<TypeMirror> implementing =
ImmutableSortedSet.orderedBy(
new Comparator<TypeMirror>() {
@Override
public int compare(TypeMirror first, TypeMirror second) {
String firstName = MoreTypes.asTypeElement(first).getQualifiedName().toString();
String secondName = MoreTypes.asTypeElement(second).getQualifiedName().toString();
return firstName.compareTo(secondName);
}
});
boolean publicType = false;
Boolean allowSubclasses = null;
boolean skipCreation = false;
for (FactoryMethodDescriptor methodDescriptor : entry.getValue()) {
extending.add(methodDescriptor.declaration().extendingType().asType());
for (TypeElement implementingType : methodDescriptor.declaration().implementingTypes()) {
implementing.add(implementingType.asType());
}
publicType |= methodDescriptor.publicMethod();
if (allowSubclasses == null) {
allowSubclasses = methodDescriptor.declaration().allowSubclasses();
} else if (!allowSubclasses.equals(methodDescriptor.declaration().allowSubclasses())) {
skipCreation = true;
messager.printMessage(Kind.ERROR,
"Cannot mix allowSubclasses=true and allowSubclasses=false in one factory.",
methodDescriptor.declaration().target(),
methodDescriptor.declaration().mirror(),
methodDescriptor.declaration().valuesMap().get("allowSubclasses"));
}
}
if (!skipCreation) {
try {
factoryWriter.writeFactory(
FactoryDescriptor.create(
entry.getKey(),
Iterables.getOnlyElement(extending.build()),
implementing.build(),
publicType,
ImmutableSet.copyOf(entry.getValue()),
implementationMethodDescriptors.get(entry.getKey()),
allowSubclasses));
} catch (IOException e) {
messager.printMessage(Kind.ERROR, "failed: " + e);
}
}
}
}
private ImmutableSet<ImplementationMethodDescriptor> implementationMethods(
TypeElement supertype, Element autoFactoryElement) {
ImmutableSet.Builder<ImplementationMethodDescriptor> implementationMethodsBuilder =
ImmutableSet.builder();
for (ExecutableElement implementationMethod :
ElementFilter.methodsIn(elements.getAllMembers(supertype))) {
if (implementationMethod.getModifiers().contains(Modifier.ABSTRACT)) {
ExecutableType methodType =
Elements2.getExecutableElementAsMemberOf(
types, implementationMethod, supertype);
ImmutableSet<Parameter> passedParameters =
Parameter.forParameterList(
implementationMethod.getParameters(), methodType.getParameterTypes(), types);
implementationMethodsBuilder.add(
ImplementationMethodDescriptor.builder()
.name(implementationMethod.getSimpleName().toString())
.returnType(getAnnotatedType(autoFactoryElement))
.publicMethod()
.passedParameters(passedParameters)
.isVarArgs(implementationMethod.isVarArgs())
.build());
}
}
return implementationMethodsBuilder.build();
}
private TypeMirror getAnnotatedType(Element element) {
List<TypeElement> types = ImmutableList.of();
while (types.isEmpty()) {
types = ElementFilter.typesIn(Arrays.asList(element));
element = element.getEnclosingElement();
}
return Iterables.getOnlyElement(types).asType();
}
@Override
public Set<String> getSupportedAnnotationTypes() {
return ImmutableSet.of(AutoFactory.class.getName(), Provided.class.getName());
}
@Override
public SourceVersion getSupportedSourceVersion() {
return SourceVersion.latestSupported();
}
}