• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright 2018 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.value.processor;
17 
18 import static com.google.auto.common.MoreElements.getLocalAndInheritedMethods;
19 import static com.google.auto.value.processor.ClassNames.AUTO_ONE_OF_NAME;
20 import static java.util.stream.Collectors.toMap;
21 import static java.util.stream.Collectors.toSet;
22 
23 import com.google.auto.common.AnnotationMirrors;
24 import com.google.auto.common.MoreElements;
25 import com.google.auto.common.MoreTypes;
26 import com.google.auto.service.AutoService;
27 import com.google.auto.value.processor.MissingTypes.MissingTypeException;
28 import com.google.common.collect.ImmutableBiMap;
29 import com.google.common.collect.ImmutableListMultimap;
30 import com.google.common.collect.ImmutableMap;
31 import com.google.common.collect.ImmutableSet;
32 import com.google.common.collect.Iterables;
33 import java.util.LinkedHashSet;
34 import java.util.Locale;
35 import java.util.Map;
36 import java.util.Optional;
37 import java.util.Set;
38 import javax.annotation.processing.Processor;
39 import javax.annotation.processing.SupportedAnnotationTypes;
40 import javax.lang.model.element.AnnotationMirror;
41 import javax.lang.model.element.AnnotationValue;
42 import javax.lang.model.element.Element;
43 import javax.lang.model.element.ElementKind;
44 import javax.lang.model.element.ExecutableElement;
45 import javax.lang.model.element.TypeElement;
46 import javax.lang.model.type.DeclaredType;
47 import javax.lang.model.type.TypeMirror;
48 import net.ltgt.gradle.incap.IncrementalAnnotationProcessor;
49 import net.ltgt.gradle.incap.IncrementalAnnotationProcessorType;
50 
51 /**
52  * Javac annotation processor (compiler plugin) for {@linkplain com.google.auto.value.AutoOneOf
53  * one-of} types; user code never references this class.
54  *
55  * @author Éamonn McManus
56  * @see <a href="https://github.com/google/auto/tree/main/value">AutoValue User's Guide</a>
57  */
58 @AutoService(Processor.class)
59 @SupportedAnnotationTypes(AUTO_ONE_OF_NAME)
60 @IncrementalAnnotationProcessor(IncrementalAnnotationProcessorType.ISOLATING)
61 public class AutoOneOfProcessor extends AutoValueishProcessor {
AutoOneOfProcessor()62   public AutoOneOfProcessor() {
63     super(AUTO_ONE_OF_NAME, /* appliesToInterfaces= */ false);
64   }
65 
66   @Override
propertiesCanBeVoid()67   boolean propertiesCanBeVoid() {
68     return true;
69   }
70 
71   @Override
getSupportedOptions()72   public ImmutableSet<String> getSupportedOptions() {
73     return ImmutableSet.of(Nullables.NULLABLE_OPTION);
74   }
75 
76   @Override
processType(TypeElement autoOneOfType)77   void processType(TypeElement autoOneOfType) {
78     DeclaredType kindMirror = mirrorForKindType(autoOneOfType);
79 
80     // We are going to classify the methods of the @AutoOneOf class into several categories.
81     // This covers the methods in the class itself and the ones it inherits from supertypes.
82     // First, the only concrete (non-abstract) methods we are interested in are overrides of
83     // Object methods (equals, hashCode, toString), which signal that we should not generate
84     // an implementation of those methods.
85     // Then, each abstract method is one of the following:
86     // (1) A property getter, like "abstract String foo()" or "abstract String getFoo()".
87     // (2) A kind getter, which is a method that returns the enum in @AutoOneOf. For
88     //     example if we have @AutoOneOf(PetKind.class), this would be a method that returns
89     //     PetKind.
90     // If there are abstract methods that don't fit any of the categories above, that is an error
91     // which we signal explicitly to avoid confusion.
92 
93     ImmutableSet<ExecutableElement> methods =
94         getLocalAndInheritedMethods(
95             autoOneOfType, processingEnv.getTypeUtils(), processingEnv.getElementUtils());
96     ImmutableSet<ExecutableElement> abstractMethods = abstractMethodsIn(methods);
97     ExecutableElement kindGetter =
98         findKindGetterOrAbort(autoOneOfType, kindMirror, abstractMethods);
99     Set<ExecutableElement> otherMethods = new LinkedHashSet<>(abstractMethods);
100     otherMethods.remove(kindGetter);
101 
102     ImmutableMap<ExecutableElement, TypeMirror> propertyMethodsAndTypes =
103         propertyMethodsIn(otherMethods, autoOneOfType);
104     ImmutableBiMap<String, ExecutableElement> properties =
105         propertyNameToMethodMap(propertyMethodsAndTypes.keySet());
106     validateMethods(autoOneOfType, abstractMethods, propertyMethodsAndTypes.keySet(), kindGetter);
107     ImmutableMap<String, String> propertyToKind =
108         propertyToKindMap(kindMirror, properties.keySet());
109 
110     String subclass = generatedClassName(autoOneOfType, "AutoOneOf_");
111     AutoOneOfTemplateVars vars = new AutoOneOfTemplateVars();
112     vars.generatedClass = TypeSimplifier.simpleNameOf(subclass);
113     vars.propertyToKind = propertyToKind;
114     Nullables nullables = Nullables.fromMethods(processingEnv, methods);
115     defineSharedVarsForType(autoOneOfType, methods, nullables, vars);
116     defineVarsForType(autoOneOfType, vars, propertyMethodsAndTypes, kindGetter, nullables);
117 
118     String text = vars.toText();
119     text = TypeEncoder.decode(text, processingEnv, vars.pkg, autoOneOfType.asType());
120     text = Reformatter.fixup(text);
121     writeSourceFile(subclass, text, autoOneOfType);
122   }
123 
mirrorForKindType(TypeElement autoOneOfType)124   private DeclaredType mirrorForKindType(TypeElement autoOneOfType) {
125     // The annotation is guaranteed to be present by the contract of Processor#process
126     AnnotationMirror oneOfAnnotation = getAnnotationMirror(autoOneOfType, AUTO_ONE_OF_NAME).get();
127     AnnotationValue kindValue = AnnotationMirrors.getAnnotationValue(oneOfAnnotation, "value");
128     Object value = kindValue.getValue();
129     if (value instanceof TypeMirror) {
130       TypeMirror kindType = (TypeMirror) value;
131       switch (kindType.getKind()) {
132         case DECLARED:
133           return MoreTypes.asDeclared(kindType);
134         case ERROR:
135           throw new MissingTypeException(MoreTypes.asError(kindType));
136         default:
137           break;
138       }
139     }
140     throw new MissingTypeException(null);
141   }
142 
propertyToKindMap( DeclaredType kindMirror, ImmutableSet<String> propertyNames)143   private ImmutableMap<String, String> propertyToKindMap(
144       DeclaredType kindMirror, ImmutableSet<String> propertyNames) {
145     // We require a one-to-one correspondence between the property names and the enum constants.
146     // We must have transformName(propertyName) = transformName(constantName) for each one.
147     // So we build two maps, transformName(propertyName) → propertyName and
148     // transformName(constantName) → constant. The key sets of the two maps must match, and we
149     // can then join them to make propertyName → constantName.
150     TypeElement kindElement = MoreElements.asType(kindMirror.asElement());
151     Map<String, String> transformedPropertyNames =
152         propertyNames.stream().collect(toMap(this::transformName, s -> s));
153     Map<String, Element> transformedEnumConstants =
154         kindElement.getEnclosedElements().stream()
155             .filter(e -> e.getKind().equals(ElementKind.ENUM_CONSTANT))
156             .collect(toMap(e -> transformName(e.getSimpleName().toString()), e -> e));
157 
158     if (transformedPropertyNames.keySet().equals(transformedEnumConstants.keySet())) {
159       ImmutableMap.Builder<String, String> mapBuilder = ImmutableMap.builder();
160       for (String transformed : transformedPropertyNames.keySet()) {
161         mapBuilder.put(
162             transformedPropertyNames.get(transformed),
163             transformedEnumConstants.get(transformed).getSimpleName().toString());
164       }
165       return mapBuilder.build();
166     }
167 
168     // The names don't match. Emit errors for the differences.
169     // Properties that have no enum constant
170     transformedPropertyNames.forEach(
171         (transformed, property) -> {
172           if (!transformedEnumConstants.containsKey(transformed)) {
173             errorReporter()
174                 .reportError(
175                     kindElement,
176                     "[AutoOneOfNoEnumConstant] Enum has no constant with name corresponding to"
177                         + " property '%s'",
178                     property);
179           }
180         });
181     // Enum constants that have no property
182     transformedEnumConstants.forEach(
183         (transformed, constant) -> {
184           if (!transformedPropertyNames.containsKey(transformed)) {
185             errorReporter()
186                 .reportError(
187                     constant,
188                     "[AutoOneOfBadEnumConstant] Name of enum constant '%s' does not correspond to"
189                         + " any property name",
190                     constant.getSimpleName());
191           }
192         });
193     throw new AbortProcessingException();
194   }
195 
transformName(String s)196   private String transformName(String s) {
197     return s.toLowerCase(Locale.ROOT).replace("_", "");
198   }
199 
findKindGetterOrAbort( TypeElement autoOneOfType, TypeMirror kindMirror, ImmutableSet<ExecutableElement> abstractMethods)200   private ExecutableElement findKindGetterOrAbort(
201       TypeElement autoOneOfType,
202       TypeMirror kindMirror,
203       ImmutableSet<ExecutableElement> abstractMethods) {
204     Set<ExecutableElement> kindGetters =
205         abstractMethods.stream()
206             .filter(e -> sameType(kindMirror, e.getReturnType()))
207             .filter(e -> e.getParameters().isEmpty())
208             .collect(toSet());
209     switch (kindGetters.size()) {
210       case 0:
211         errorReporter()
212             .reportError(
213                 autoOneOfType,
214                 "[AutoOneOfNoKindGetter] %s must have a no-arg abstract method returning %s",
215                 autoOneOfType,
216                 kindMirror);
217         break;
218       case 1:
219         return Iterables.getOnlyElement(kindGetters);
220       default:
221         for (ExecutableElement getter : kindGetters) {
222           errorReporter()
223               .reportError(
224                   getter,
225                   "[AutoOneOfTwoKindGetters] More than one abstract method returns %s",
226                   kindMirror);
227         }
228     }
229     throw new AbortProcessingException();
230   }
231 
validateMethods( TypeElement type, ImmutableSet<ExecutableElement> abstractMethods, ImmutableSet<ExecutableElement> propertyMethods, ExecutableElement kindGetter)232   private void validateMethods(
233       TypeElement type,
234       ImmutableSet<ExecutableElement> abstractMethods,
235       ImmutableSet<ExecutableElement> propertyMethods,
236       ExecutableElement kindGetter) {
237     for (ExecutableElement method : abstractMethods) {
238       if (propertyMethods.contains(method)) {
239         checkReturnType(type, method);
240       } else if (!method.equals(kindGetter)
241           && objectMethodToOverride(method) == ObjectMethod.NONE) {
242         // This could reasonably be an error, were it not for an Eclipse bug in
243         // ElementUtils.override that sometimes fails to recognize that one method overrides
244         // another, and therefore leaves us with both an abstract method and the subclass method
245         // that overrides it. This shows up in AutoValueTest.LukesBase for example.
246         // The compilation will fail anyway because the generated concrete classes won't
247         // implement this alien method.
248         errorReporter()
249             .reportWarning(
250                 method,
251                 "[AutoOneOfParams] Abstract methods in @AutoOneOf classes must have no parameters");
252       }
253     }
254     errorReporter().abortIfAnyError();
255   }
256 
defineVarsForType( TypeElement type, AutoOneOfTemplateVars vars, ImmutableMap<ExecutableElement, TypeMirror> propertyMethodsAndTypes, ExecutableElement kindGetter, Nullables nullables)257   private void defineVarsForType(
258       TypeElement type,
259       AutoOneOfTemplateVars vars,
260       ImmutableMap<ExecutableElement, TypeMirror> propertyMethodsAndTypes,
261       ExecutableElement kindGetter,
262       Nullables nullables) {
263     vars.props =
264         propertySet(
265             propertyMethodsAndTypes,
266             /* annotatedPropertyFields= */ ImmutableListMultimap.of(),
267             /* annotatedPropertyMethods= */ ImmutableListMultimap.of(),
268             nullables);
269     vars.kindGetter = kindGetter.getSimpleName().toString();
270     vars.kindType = TypeEncoder.encode(kindGetter.getReturnType());
271     TypeElement javaIoSerializable = elementUtils().getTypeElement("java.io.Serializable");
272     vars.serializable =
273         javaIoSerializable != null // just in case
274             && typeUtils().isAssignable(type.asType(), javaIoSerializable.asType());
275   }
276 
277   @Override
nullableAnnotationForMethod(ExecutableElement propertyMethod)278   Optional<String> nullableAnnotationForMethod(ExecutableElement propertyMethod) {
279     if (nullableAnnotationFor(propertyMethod, propertyMethod.getReturnType()).isPresent()) {
280       errorReporter()
281           .reportError(
282               propertyMethod, "[AutoOneOfNullable] @AutoOneOf properties cannot be @Nullable");
283     }
284     return Optional.empty();
285   }
286 
sameType(TypeMirror t1, TypeMirror t2)287   private static boolean sameType(TypeMirror t1, TypeMirror t2) {
288     return MoreTypes.equivalence().equivalent(t1, t2);
289   }
290 }
291