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