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