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