com.google.auto.value.processor.AutoOneOfProcessor 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 2018 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_ONE_OF_NAME;
import static java.util.stream.Collectors.toMap;
import static java.util.stream.Collectors.toSet;
import com.google.auto.common.AnnotationMirrors;
import com.google.auto.common.MoreElements;
import com.google.auto.common.MoreTypes;
import com.google.auto.service.AutoService;
import com.google.common.collect.ImmutableBiMap;
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.util.LinkedHashSet;
import java.util.Locale;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import javax.annotation.processing.Processor;
import javax.annotation.processing.SupportedAnnotationTypes;
import javax.lang.model.element.AnnotationMirror;
import javax.lang.model.element.AnnotationValue;
import javax.lang.model.element.Element;
import javax.lang.model.element.ElementKind;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.TypeElement;
import javax.lang.model.type.DeclaredType;
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 {@linkplain com.google.auto.value.AutoOneOf
* one-of} types; user code never references this class.
*
* @author Éamonn McManus
* @see AutoValue User's Guide
*/
@AutoService(Processor.class)
@SupportedAnnotationTypes(AUTO_ONE_OF_NAME)
@IncrementalAnnotationProcessor(IncrementalAnnotationProcessorType.ISOLATING)
public class AutoOneOfProcessor extends AutoValueOrOneOfProcessor {
public AutoOneOfProcessor() {
super(AUTO_ONE_OF_NAME);
}
@Override
boolean propertiesCanBeVoid() {
return true;
}
@Override
void processType(TypeElement autoOneOfType) {
if (autoOneOfType.getKind() != ElementKind.CLASS) {
errorReporter()
.abortWithError("@" + AUTO_ONE_OF_NAME + " only applies to classes", autoOneOfType);
}
checkModifiersIfNested(autoOneOfType);
DeclaredType kindMirror = mirrorForKindType(autoOneOfType);
// We are going to classify the methods of the @AutoOneOf 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 kind getter, which is a method that returns the enum in @AutoOneOf. For
// example if we have @AutoOneOf(PetKind.class), this would be a method that returns
// PetKind.
// 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(
autoOneOfType, processingEnv.getTypeUtils(), processingEnv.getElementUtils());
ImmutableSet abstractMethods = abstractMethodsIn(methods);
ExecutableElement kindGetter =
findKindGetterOrAbort(autoOneOfType, kindMirror, abstractMethods);
Set otherMethods = new LinkedHashSet<>(abstractMethods);
otherMethods.remove(kindGetter);
ImmutableMap propertyMethodsAndTypes =
propertyMethodsIn(otherMethods, autoOneOfType);
ImmutableBiMap properties =
propertyNameToMethodMap(propertyMethodsAndTypes.keySet());
validateMethods(autoOneOfType, abstractMethods, propertyMethodsAndTypes.keySet(), kindGetter);
ImmutableMap propertyToKind =
propertyToKindMap(kindMirror, properties.keySet());
String subclass = generatedClassName(autoOneOfType, "AutoOneOf_");
AutoOneOfTemplateVars vars = new AutoOneOfTemplateVars();
vars.generatedClass = TypeSimplifier.simpleNameOf(subclass);
vars.propertyToKind = propertyToKind;
defineSharedVarsForType(autoOneOfType, methods, vars);
defineVarsForType(autoOneOfType, vars, propertyMethodsAndTypes, kindGetter);
String text = vars.toText();
text = TypeEncoder.decode(text, processingEnv, vars.pkg, autoOneOfType.asType());
text = Reformatter.fixup(text);
writeSourceFile(subclass, text, autoOneOfType);
}
private DeclaredType mirrorForKindType(TypeElement autoOneOfType) {
Optional oneOfAnnotation =
getAnnotationMirror(autoOneOfType, AUTO_ONE_OF_NAME);
if (!oneOfAnnotation.isPresent()) {
// 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 @AutoOneOf was invoked with a type"
+ " that does not have that annotation; this is probably a compiler bug",
autoOneOfType);
}
AnnotationValue kindValue =
AnnotationMirrors.getAnnotationValue(oneOfAnnotation.get(), "value");
Object value = kindValue.getValue();
if (value instanceof TypeMirror && ((TypeMirror) value).getKind().equals(TypeKind.DECLARED)) {
return MoreTypes.asDeclared((TypeMirror) value);
} else {
// This is presumably because the referenced type doesn't exist.
throw new MissingTypeException();
}
}
private ImmutableMap propertyToKindMap(
DeclaredType kindMirror, ImmutableSet propertyNames) {
// We require a one-to-one correspondence between the property names and the enum constants.
// We must have transformName(propertyName) = transformName(constantName) for each one.
// So we build two maps, transformName(propertyName) → propertyName and
// transformName(constantName) → constant. The key sets of the two maps must match, and we
// can then join them to make propertyName → constantName.
TypeElement kindElement = MoreElements.asType(kindMirror.asElement());
Map transformedPropertyNames =
propertyNames.stream().collect(toMap(this::transformName, s -> s));
Map transformedEnumConstants =
kindElement
.getEnclosedElements()
.stream()
.filter(e -> e.getKind().equals(ElementKind.ENUM_CONSTANT))
.collect(toMap(e -> transformName(e.getSimpleName().toString()), e -> e));
if (transformedPropertyNames.keySet().equals(transformedEnumConstants.keySet())) {
ImmutableMap.Builder mapBuilder = ImmutableMap.builder();
for (String transformed : transformedPropertyNames.keySet()) {
mapBuilder.put(
transformedPropertyNames.get(transformed),
transformedEnumConstants.get(transformed).getSimpleName().toString());
}
return mapBuilder.build();
}
// The names don't match. Emit errors for the differences.
// Properties that have no enum constant
transformedPropertyNames.forEach(
(transformed, property) -> {
if (!transformedEnumConstants.containsKey(transformed)) {
errorReporter()
.reportError(
"Enum has no constant with name corresponding to property '" + property + "'",
kindElement);
}
});
// Enum constants that have no property
transformedEnumConstants.forEach(
(transformed, constant) -> {
if (!transformedPropertyNames.containsKey(transformed)) {
errorReporter()
.reportError(
"Name of enum constant '"
+ constant.getSimpleName()
+ "' does not correspond to any property name",
constant);
}
});
throw new AbortProcessingException();
}
private String transformName(String s) {
return s.toLowerCase(Locale.ROOT).replace("_", "");
}
private ExecutableElement findKindGetterOrAbort(
TypeElement autoOneOfType,
TypeMirror kindMirror,
ImmutableSet abstractMethods) {
Set kindGetters =
abstractMethods
.stream()
.filter(e -> sameType(kindMirror, e.getReturnType()))
.filter(e -> e.getParameters().isEmpty())
.collect(toSet());
switch (kindGetters.size()) {
case 0:
errorReporter()
.reportError(
autoOneOfType + " must have a no-arg abstract method returning " + kindMirror,
autoOneOfType);
break;
case 1:
return Iterables.getOnlyElement(kindGetters);
default:
for (ExecutableElement getter : kindGetters) {
errorReporter()
.reportError("More than one abstract method returns " + kindMirror, getter);
}
}
throw new AbortProcessingException();
}
private void validateMethods(
TypeElement type,
ImmutableSet abstractMethods,
ImmutableSet propertyMethods,
ExecutableElement kindGetter) {
for (ExecutableElement method : abstractMethods) {
if (propertyMethods.contains(method)) {
checkReturnType(type, method);
} else if (!method.equals(kindGetter)
&& 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.
// The compilation will fail anyway because the generated concrete classes won't
// implement this alien method.
errorReporter()
.reportWarning(
"Abstract methods in @AutoOneOf classes must have no parameters",
method);
}
}
errorReporter().abortIfAnyError();
}
private void defineVarsForType(
TypeElement type,
AutoOneOfTemplateVars vars,
ImmutableMap propertyMethodsAndTypes,
ExecutableElement kindGetter) {
vars.props = propertySet(
propertyMethodsAndTypes, ImmutableListMultimap.of(), ImmutableListMultimap.of());
vars.kindGetter = kindGetter.getSimpleName().toString();
vars.kindType = TypeEncoder.encode(kindGetter.getReturnType());
TypeElement javaIoSerializable = elementUtils().getTypeElement("java.io.Serializable");
vars.serializable =
javaIoSerializable != null // just in case
&& typeUtils().isAssignable(type.asType(), javaIoSerializable.asType());
}
@Override
Optional nullableAnnotationForMethod(ExecutableElement propertyMethod) {
if (nullableAnnotationFor(propertyMethod, propertyMethod.getReturnType()).isPresent()) {
errorReporter().reportError("@AutoOneOf properties cannot be @Nullable", propertyMethod);
}
return Optional.empty();
}
private static boolean sameType(TypeMirror t1, TypeMirror t2) {
return MoreTypes.equivalence().equivalent(t1, t2);
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy