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