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