com.google.auto.value.processor.AutoValueProcessor Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of auto-value Show documentation
Show all versions of auto-value Show documentation
Immutable value-type code generation for Java 1.6+.
/*
* Copyright 2012 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.value.processor;
import static com.google.auto.common.MoreElements.getLocalAndInheritedMethods;
import static com.google.auto.value.processor.ClassNames.AUTO_VALUE_NAME;
import static com.google.common.collect.Sets.difference;
import static com.google.common.collect.Sets.intersection;
import static java.util.stream.Collectors.joining;
import static java.util.stream.Collectors.toList;
import com.google.auto.service.AutoService;
import com.google.auto.value.extension.AutoValueExtension;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Strings;
import com.google.common.base.Throwables;
import com.google.common.collect.ImmutableBiMap;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableListMultimap;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import java.lang.annotation.Annotation;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
import java.util.List;
import java.util.Optional;
import java.util.ServiceConfigurationError;
import java.util.Set;
import javax.annotation.processing.ProcessingEnvironment;
import javax.annotation.processing.Processor;
import javax.annotation.processing.SupportedAnnotationTypes;
import javax.lang.model.element.AnnotationMirror;
import javax.lang.model.element.ElementKind;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.TypeElement;
import javax.lang.model.type.TypeKind;
import javax.lang.model.type.TypeMirror;
import net.ltgt.gradle.incap.IncrementalAnnotationProcessor;
import net.ltgt.gradle.incap.IncrementalAnnotationProcessorType;
/**
* Javac annotation processor (compiler plugin) for value types; user code never references this
* class.
*
* @see AutoValue User's Guide
* @author Éamonn McManus
*/
@AutoService(Processor.class)
@SupportedAnnotationTypes(AUTO_VALUE_NAME)
@IncrementalAnnotationProcessor(IncrementalAnnotationProcessorType.DYNAMIC)
public class AutoValueProcessor extends AutoValueOrOneOfProcessor {
private static final String OMIT_IDENTIFIERS_OPTION = "com.google.auto.value.OmitIdentifiers";
// We moved MemoizeExtension to a different package, which had an unexpected effect:
// now if an old version of AutoValue is in the class path, ServiceLoader can pick up both the
// old and the new versions of MemoizeExtension. So we exclude the old version if we see it.
// The new version will be bundled with this processor so we should always find it.
private static final String OLD_MEMOIZE_EXTENSION =
"com.google.auto.value.extension.memoized.MemoizeExtension";
public AutoValueProcessor() {
this(AutoValueProcessor.class.getClassLoader());
}
@VisibleForTesting
AutoValueProcessor(ClassLoader loaderForExtensions) {
super(AUTO_VALUE_NAME);
this.extensions = null;
this.loaderForExtensions = loaderForExtensions;
}
@VisibleForTesting
public AutoValueProcessor(Iterable extends AutoValueExtension> extensions) {
super(AUTO_VALUE_NAME);
this.extensions = ImmutableList.copyOf(extensions);
this.loaderForExtensions = null;
}
// Depending on how this AutoValueProcessor was constructed, we might already have a list of
// extensions when init() is run, or, if `extensions` is null, we have a ClassLoader that will be
// used to get the list using the ServiceLoader API.
private ImmutableList extensions;
private final ClassLoader loaderForExtensions;
@VisibleForTesting
static ImmutableList extensionsFromLoader(ClassLoader loader) {
return ImmutableList.copyOf(
Iterables.filter(
SimpleServiceLoader.load(AutoValueExtension.class, loader),
ext -> !ext.getClass().getName().equals(OLD_MEMOIZE_EXTENSION)));
}
@Override
public synchronized void init(ProcessingEnvironment processingEnv) {
super.init(processingEnv);
if (extensions == null) {
try {
extensions = extensionsFromLoader(loaderForExtensions);
} catch (RuntimeException | Error e) {
StringBuilder warning = new StringBuilder();
warning.append(
"An exception occurred while looking for AutoValue extensions."
+ " No extensions will function.");
if (e instanceof ServiceConfigurationError) {
warning.append(" This may be due to a corrupt jar file in the compiler's classpath.");
}
warning.append("\n").append(Throwables.getStackTraceAsString(e));
errorReporter().reportWarning(warning.toString(), null);
extensions = ImmutableList.of();
}
}
}
@Override
public Set getSupportedOptions() {
ImmutableSet.Builder builder = ImmutableSet.builder();
AutoValueExtension.IncrementalExtensionType incrementalType =
extensions.stream()
.map(e -> e.incrementalType(processingEnv))
.min(Comparator.naturalOrder())
.orElse(AutoValueExtension.IncrementalExtensionType.ISOLATING);
builder.add(OMIT_IDENTIFIERS_OPTION).addAll(optionsFor(incrementalType));
for (AutoValueExtension extension : extensions) {
builder.addAll(extension.getSupportedOptions());
}
return builder.build();
}
private static ImmutableSet optionsFor(
AutoValueExtension.IncrementalExtensionType incrementalType) {
switch (incrementalType) {
case ISOLATING:
return ImmutableSet.of(IncrementalAnnotationProcessorType.ISOLATING.getProcessorOption());
case AGGREGATING:
return ImmutableSet.of(IncrementalAnnotationProcessorType.AGGREGATING.getProcessorOption());
case UNKNOWN:
return ImmutableSet.of();
}
throw new AssertionError(incrementalType);
}
static String generatedSubclassName(TypeElement type, int depth) {
return generatedClassName(type, Strings.repeat("$", depth) + "AutoValue_");
}
@Override
void processType(TypeElement type) {
if (!hasAnnotationMirror(type, AUTO_VALUE_NAME)) {
// This shouldn't happen unless the compilation environment is buggy,
// but it has happened in the past and can crash the compiler.
errorReporter()
.abortWithError(
"annotation processor for @AutoValue was invoked with a type"
+ " that does not have that annotation; this is probably a compiler bug",
type);
}
if (type.getKind() != ElementKind.CLASS) {
errorReporter().abortWithError("@AutoValue only applies to classes", type);
}
if (ancestorIsAutoValue(type)) {
errorReporter().abortWithError("One @AutoValue class may not extend another", type);
}
if (implementsAnnotation(type)) {
errorReporter()
.abortWithError(
"@AutoValue may not be used to implement an annotation"
+ " interface; try using @AutoAnnotation instead",
type);
}
checkModifiersIfNested(type);
// We are going to classify the methods of the @AutoValue class into several categories.
// This covers the methods in the class itself and the ones it inherits from supertypes.
// First, the only concrete (non-abstract) methods we are interested in are overrides of
// Object methods (equals, hashCode, toString), which signal that we should not generate
// an implementation of those methods.
// Then, each abstract method is one of the following:
// (1) A property getter, like "abstract String foo()" or "abstract String getFoo()".
// (2) A toBuilder() method, which is any abstract no-arg method returning the Builder for
// this @AutoValue class.
// (3) An abstract method that will be consumed by an extension, such as
// Parcelable.describeContents() or Parcelable.writeToParcel(Parcel, int).
// The describeContents() example shows a quirk here: initially we will identify it as a
// property, which means that we need to reconstruct the list of properties after allowing
// extensions to consume abstract methods.
// If there are abstract methods that don't fit any of the categories above, that is an error
// which we signal explicitly to avoid confusion.
ImmutableSet methods =
getLocalAndInheritedMethods(
type, processingEnv.getTypeUtils(), processingEnv.getElementUtils());
ImmutableSet abstractMethods = abstractMethodsIn(methods);
BuilderSpec builderSpec = new BuilderSpec(type, processingEnv, errorReporter());
Optional builder = builderSpec.getBuilder();
ImmutableSet toBuilderMethods;
if (builder.isPresent()) {
toBuilderMethods = builder.get().toBuilderMethods(typeUtils(), abstractMethods);
} else {
toBuilderMethods = ImmutableSet.of();
}
ImmutableMap propertyMethodsAndTypes =
propertyMethodsIn(immutableSetDifference(abstractMethods, toBuilderMethods), type);
ImmutableMap properties =
propertyNameToMethodMap(propertyMethodsAndTypes.keySet());
ExtensionContext context = new ExtensionContext(
processingEnv, type, properties, propertyMethodsAndTypes, abstractMethods);
ImmutableList applicableExtensions = applicableExtensions(type, context);
ImmutableSet consumedMethods =
methodsConsumedByExtensions(
type, applicableExtensions, context, abstractMethods, properties);
if (!consumedMethods.isEmpty()) {
ImmutableSet allAbstractMethods = abstractMethods;
abstractMethods = immutableSetDifference(abstractMethods, consumedMethods);
toBuilderMethods = immutableSetDifference(toBuilderMethods, consumedMethods);
propertyMethodsAndTypes =
propertyMethodsIn(immutableSetDifference(abstractMethods, toBuilderMethods), type);
properties = propertyNameToMethodMap(propertyMethodsAndTypes.keySet());
context = new ExtensionContext(
processingEnv, type, properties, propertyMethodsAndTypes, allAbstractMethods);
}
ImmutableSet propertyMethods = propertyMethodsAndTypes.keySet();
boolean extensionsPresent = !applicableExtensions.isEmpty();
validateMethods(type, abstractMethods, toBuilderMethods, propertyMethods, extensionsPresent);
String finalSubclass = generatedSubclassName(type, 0);
AutoValueTemplateVars vars = new AutoValueTemplateVars();
vars.finalSubclass = TypeSimplifier.simpleNameOf(finalSubclass);
vars.types = processingEnv.getTypeUtils();
vars.identifiers = !processingEnv.getOptions().containsKey(OMIT_IDENTIFIERS_OPTION);
defineSharedVarsForType(type, methods, vars);
defineVarsForType(type, vars, toBuilderMethods, propertyMethodsAndTypes, builder);
// If we've encountered problems then we might end up invoking extensions with inconsistent
// state. Anyway we probably don't want to generate code which is likely to provoke further
// compile errors to add to the ones we've already seen.
errorReporter().abortIfAnyError();
GwtCompatibility gwtCompatibility = new GwtCompatibility(type);
vars.gwtCompatibleAnnotation = gwtCompatibility.gwtCompatibleAnnotationString();
builder.ifPresent(context::setBuilderContext);
int subclassDepth = writeExtensions(type, context, applicableExtensions);
String subclass = generatedSubclassName(type, subclassDepth);
vars.subclass = TypeSimplifier.simpleNameOf(subclass);
vars.isFinal = (subclassDepth == 0);
vars.modifiers = vars.isFinal ? "final " : "abstract ";
String text = vars.toText();
text = TypeEncoder.decode(text, processingEnv, vars.pkg, type.asType());
text = Reformatter.fixup(text);
writeSourceFile(subclass, text, type);
GwtSerialization gwtSerialization = new GwtSerialization(gwtCompatibility, processingEnv, type);
gwtSerialization.maybeWriteGwtSerializer(vars);
}
// Invokes each of the given extensions to generate its subclass, and returns the number of
// hierarchy classes that extensions generated. This number is then the number of $ characters
// that should precede the name of the AutoValue implementation class.
// Assume the @AutoValue class is com.example.Foo.Bar. Then if there are no
// extensions the returned value will be 0, so the AutoValue implementation will be
// com.example.AutoValue_Foo_Bar. If there is one extension, it will be asked to
// generate AutoValue_Foo_Bar with parent $AutoValue_Foo_Bar. If it does so (returns
// non-null) then the returned value will be 1, so the AutoValue implementation will be
// com.example.$AutoValue_Foo_Bar. Otherwise, the returned value will still be 0. Likewise,
// if there is a second extension and both extensions return non-null, the first one will
// generate AutoValue_Foo_Bar with parent $AutoValue_Foo_Bar, the second will generate
// $AutoValue_Foo_Bar with parent $$AutoValue_Foo_Bar, and the returned value will be 2 for
// com.example.$$AutoValue_Foo_Bar.
private int writeExtensions(
TypeElement type,
ExtensionContext context,
ImmutableList applicableExtensions) {
int writtenSoFar = 0;
for (AutoValueExtension extension : applicableExtensions) {
String parentFqName = generatedSubclassName(type, writtenSoFar + 1);
String parentSimpleName = TypeSimplifier.simpleNameOf(parentFqName);
String classFqName = generatedSubclassName(type, writtenSoFar);
String classSimpleName = TypeSimplifier.simpleNameOf(classFqName);
boolean isFinal = (writtenSoFar == 0);
String source = extension.generateClass(context, classSimpleName, parentSimpleName, isFinal);
if (source != null) {
source = Reformatter.fixup(source);
writeSourceFile(classFqName, source, type);
writtenSoFar++;
}
}
return writtenSoFar;
}
private ImmutableList applicableExtensions(
TypeElement type, ExtensionContext context) {
List applicableExtensions = new ArrayList<>();
List finalExtensions = new ArrayList<>();
for (AutoValueExtension extension : extensions) {
if (extension.applicable(context)) {
if (extension.mustBeFinal(context)) {
finalExtensions.add(extension);
} else {
applicableExtensions.add(extension);
}
}
}
switch (finalExtensions.size()) {
case 0:
break;
case 1:
applicableExtensions.add(0, finalExtensions.get(0));
break;
default:
errorReporter()
.reportError(
"More than one extension wants to generate the final class: "
+ finalExtensions.stream().map(this::extensionName).collect(joining(", ")),
type);
break;
}
return ImmutableList.copyOf(applicableExtensions);
}
private ImmutableSet methodsConsumedByExtensions(
TypeElement type,
ImmutableList applicableExtensions,
ExtensionContext context,
ImmutableSet abstractMethods,
ImmutableMap properties) {
Set consumed = new HashSet<>();
for (AutoValueExtension extension : applicableExtensions) {
Set consumedHere = new HashSet<>();
for (String consumedProperty : extension.consumeProperties(context)) {
ExecutableElement propertyMethod = properties.get(consumedProperty);
if (propertyMethod == null) {
errorReporter()
.reportError(
"Extension "
+ extensionName(extension)
+ " wants to consume a property that does not exist: "
+ consumedProperty,
type);
} else {
consumedHere.add(propertyMethod);
}
}
for (ExecutableElement consumedMethod : extension.consumeMethods(context)) {
if (!abstractMethods.contains(consumedMethod)) {
errorReporter()
.reportError(
"Extension "
+ extensionName(extension)
+ " wants to consume a method that is not one of the abstract methods in this"
+ " class: "
+ consumedMethod,
type);
} else {
consumedHere.add(consumedMethod);
}
}
for (ExecutableElement repeat : intersection(consumed, consumedHere)) {
errorReporter()
.reportError(
"Extension "
+ extensionName(extension)
+ " wants to consume a method that was already consumed by another extension",
repeat);
}
consumed.addAll(consumedHere);
}
return ImmutableSet.copyOf(consumed);
}
private void validateMethods(
TypeElement type,
ImmutableSet abstractMethods,
ImmutableSet toBuilderMethods,
ImmutableSet propertyMethods,
boolean extensionsPresent) {
for (ExecutableElement method : abstractMethods) {
if (propertyMethods.contains(method)) {
checkReturnType(type, method);
} else if (!toBuilderMethods.contains(method)
&& objectMethodToOverride(method) == ObjectMethod.NONE) {
// This could reasonably be an error, were it not for an Eclipse bug in
// ElementUtils.override that sometimes fails to recognize that one method overrides
// another, and therefore leaves us with both an abstract method and the subclass method
// that overrides it. This shows up in AutoValueTest.LukesBase for example.
String message = "Abstract method is neither a property getter nor a Builder converter";
if (extensionsPresent) {
message += ", and no extension consumed it";
}
errorReporter().reportWarning(message, method);
}
}
errorReporter().abortIfAnyError();
}
private String extensionName(AutoValueExtension extension) {
return extension.getClass().getName();
}
private void defineVarsForType(
TypeElement type,
AutoValueTemplateVars vars,
ImmutableSet toBuilderMethods,
ImmutableMap propertyMethodsAndTypes,
Optional builder) {
ImmutableSet propertyMethods = propertyMethodsAndTypes.keySet();
// We can't use ImmutableList.toImmutableList() for obscure Google-internal reasons.
vars.toBuilderMethods =
ImmutableList.copyOf(toBuilderMethods.stream().map(SimpleMethod::new).collect(toList()));
ImmutableListMultimap annotatedPropertyFields =
propertyFieldAnnotationMap(type, propertyMethods);
ImmutableListMultimap annotatedPropertyMethods =
propertyMethodAnnotationMap(type, propertyMethods);
vars.props =
propertySet(propertyMethodsAndTypes, annotatedPropertyFields, annotatedPropertyMethods);
vars.serialVersionUID = getSerialVersionUID(type);
// Check for @AutoValue.Builder and add appropriate variables if it is present.
if (builder.isPresent()) {
ImmutableBiMap methodToPropertyName =
propertyNameToMethodMap(propertyMethods).inverse();
builder.get().defineVars(vars, methodToPropertyName);
}
}
@Override
Optional nullableAnnotationForMethod(ExecutableElement propertyMethod) {
return nullableAnnotationFor(propertyMethod, propertyMethod.getReturnType());
}
static ImmutableSet prefixedGettersIn(Iterable methods) {
ImmutableSet.Builder getters = ImmutableSet.builder();
for (ExecutableElement method : methods) {
String name = method.getSimpleName().toString();
// TODO(emcmanus): decide whether getfoo() (without a capital) is a getter. Currently it is.
boolean get = name.startsWith("get") && !name.equals("get");
boolean is =
name.startsWith("is")
&& !name.equals("is")
&& method.getReturnType().getKind() == TypeKind.BOOLEAN;
if (get || is) {
getters.add(method);
}
}
return getters.build();
}
private boolean ancestorIsAutoValue(TypeElement type) {
while (true) {
TypeMirror parentMirror = type.getSuperclass();
if (parentMirror.getKind() == TypeKind.NONE) {
return false;
}
TypeElement parentElement = (TypeElement) typeUtils().asElement(parentMirror);
if (hasAnnotationMirror(parentElement, AUTO_VALUE_NAME)) {
return true;
}
type = parentElement;
}
}
private boolean implementsAnnotation(TypeElement type) {
return typeUtils().isAssignable(type.asType(), getTypeMirror(Annotation.class));
}
private TypeMirror getTypeMirror(Class> c) {
return processingEnv.getElementUtils().getTypeElement(c.getName()).asType();
}
private static ImmutableSet immutableSetDifference(ImmutableSet a, ImmutableSet b) {
if (Collections.disjoint(a, b)) {
return a;
} else {
return ImmutableSet.copyOf(difference(a, b));
}
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy