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