1 /* 2 * Copyright 2015 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.value.processor.AutoValueishProcessor.nullableAnnotationFor; 19 20 import com.google.auto.common.MoreElements; 21 import com.google.auto.common.MoreTypes; 22 import com.google.auto.value.processor.BuilderSpec.Copier; 23 import com.google.auto.value.processor.BuilderSpec.PropertySetter; 24 import com.google.auto.value.processor.PropertyBuilderClassifier.PropertyBuilder; 25 import com.google.common.base.Equivalence; 26 import com.google.common.collect.ImmutableBiMap; 27 import com.google.common.collect.ImmutableList; 28 import com.google.common.collect.ImmutableMap; 29 import com.google.common.collect.ImmutableMultimap; 30 import com.google.common.collect.ImmutableSet; 31 import com.google.common.collect.Iterables; 32 import com.google.common.collect.LinkedListMultimap; 33 import com.google.common.collect.Multimap; 34 import java.util.LinkedHashMap; 35 import java.util.LinkedHashSet; 36 import java.util.Map; 37 import java.util.Optional; 38 import java.util.Set; 39 import java.util.function.Function; 40 import java.util.stream.Stream; 41 import javax.annotation.processing.ProcessingEnvironment; 42 import javax.lang.model.element.Element; 43 import javax.lang.model.element.ExecutableElement; 44 import javax.lang.model.element.Modifier; 45 import javax.lang.model.element.TypeElement; 46 import javax.lang.model.element.VariableElement; 47 import javax.lang.model.type.DeclaredType; 48 import javax.lang.model.type.ExecutableType; 49 import javax.lang.model.type.TypeKind; 50 import javax.lang.model.type.TypeMirror; 51 import javax.lang.model.util.ElementFilter; 52 import javax.lang.model.util.Elements; 53 import javax.lang.model.util.Types; 54 55 /** 56 * Classifies methods inside builder types, based on their names and parameter and return types. 57 * 58 * @param <E> the kind of {@link Element} that the corresponding properties are defined by. This is 59 * {@link ExecutableElement} for AutoValue, where properties are defined by abstract methods, 60 * and {@link VariableElement} for AutoBuilder, where they are defined by constructor or method 61 * parameters. 62 * @author Éamonn McManus 63 */ 64 abstract class BuilderMethodClassifier<E extends Element> { 65 private static final Equivalence<TypeMirror> TYPE_EQUIVALENCE = MoreTypes.equivalence(); 66 67 private final ErrorReporter errorReporter; 68 private final Types typeUtils; 69 private final Elements elementUtils; 70 private final TypeMirror builtType; 71 private final TypeElement builderType; 72 private final ImmutableSet<String> propertiesWithDefaults; 73 74 /** 75 * Property types, rewritten to refer to type variables in the builder. For example, suppose you 76 * have {@code @AutoValue abstract class Foo<T>} with a getter {@code abstract T bar()} and a 77 * builder {@code @AutoValue.Builder interface Builder<T>} with a setter {@code abstract 78 * Builder<T> setBar(T t)}. Then the {@code T} of {@code Foo<T>} and the {@code T} of {@code 79 * Foo.Builder<T>} are two separate variables. Originally {@code bar()} returned the {@code T} of 80 * {@code Foo<T>}, but in this map we have rewritten it to be the {@code T} of {@code 81 * Foo.Builder<T>}. 82 * 83 * <p>Importantly, this rewrite <b>loses type annotations</b>, so when those are important we must 84 * be careful to look at the original type as reported by the {@link #originalPropertyType} 85 * method. 86 */ 87 private final ImmutableMap<String, TypeMirror> rewrittenPropertyTypes; 88 89 private final Set<ExecutableElement> buildMethods = new LinkedHashSet<>(); 90 private final Map<String, BuilderSpec.PropertyGetter> builderGetters = new LinkedHashMap<>(); 91 private final Map<String, PropertyBuilder> propertyNameToPropertyBuilder = new LinkedHashMap<>(); 92 private final Multimap<String, PropertySetter> propertyNameToPrefixedSetters = 93 LinkedListMultimap.create(); 94 private final Multimap<String, PropertySetter> propertyNameToUnprefixedSetters = 95 LinkedListMultimap.create(); 96 private final EclipseHack eclipseHack; 97 private final Nullables nullables; 98 99 private boolean settersPrefixed; 100 BuilderMethodClassifier( ErrorReporter errorReporter, ProcessingEnvironment processingEnv, TypeMirror builtType, TypeElement builderType, ImmutableMap<String, TypeMirror> rewrittenPropertyTypes, ImmutableSet<String> propertiesWithDefaults, Nullables nullables)101 BuilderMethodClassifier( 102 ErrorReporter errorReporter, 103 ProcessingEnvironment processingEnv, 104 TypeMirror builtType, 105 TypeElement builderType, 106 ImmutableMap<String, TypeMirror> rewrittenPropertyTypes, 107 ImmutableSet<String> propertiesWithDefaults, 108 Nullables nullables) { 109 this.errorReporter = errorReporter; 110 this.typeUtils = processingEnv.getTypeUtils(); 111 this.elementUtils = processingEnv.getElementUtils(); 112 this.builtType = builtType; 113 this.builderType = builderType; 114 this.rewrittenPropertyTypes = rewrittenPropertyTypes; 115 this.propertiesWithDefaults = propertiesWithDefaults; 116 this.eclipseHack = new EclipseHack(processingEnv); 117 this.nullables = nullables; 118 } 119 120 /** 121 * Returns a multimap from the name of a property to the methods that set it. If the property is 122 * defined by an abstract method in the {@code @AutoValue} class called {@code foo()} or {@code 123 * getFoo()} then the name of the property is {@code foo} and there will be an entry in the map 124 * where the key is {@code "foo"} and the value describes a method in the builder called {@code 125 * foo} or {@code setFoo}. 126 */ propertyNameToSetters()127 ImmutableMultimap<String, PropertySetter> propertyNameToSetters() { 128 return ImmutableMultimap.copyOf( 129 settersPrefixed ? propertyNameToPrefixedSetters : propertyNameToUnprefixedSetters); 130 } 131 propertyNameToPropertyBuilder()132 Map<String, PropertyBuilder> propertyNameToPropertyBuilder() { 133 return propertyNameToPropertyBuilder; 134 } 135 136 /** 137 * Returns the set of properties that have getters in the builder. If a property is defined by an 138 * abstract method in the {@code @AutoValue} class called {@code foo()} or {@code getFoo()} then 139 * the name of the property is {@code foo}, If the builder also has a method of the same name 140 * ({@code foo()} or {@code getFoo()}) then the set returned here will contain {@code foo}. 141 */ builderGetters()142 ImmutableMap<String, BuilderSpec.PropertyGetter> builderGetters() { 143 return ImmutableMap.copyOf(builderGetters); 144 } 145 146 /** 147 * Returns the methods that were identified as {@code build()} methods. These are methods that 148 * have no parameters and return the {@code @AutoValue} type, conventionally called {@code 149 * build()}. 150 */ buildMethods()151 Set<ExecutableElement> buildMethods() { 152 return ImmutableSet.copyOf(buildMethods); 153 } 154 155 /** Classifies the given methods and sets the state of this object based on what is found. */ classifyMethods(Iterable<ExecutableElement> methods, boolean autoValueHasToBuilder)156 boolean classifyMethods(Iterable<ExecutableElement> methods, boolean autoValueHasToBuilder) { 157 int startErrorCount = errorReporter.errorCount(); 158 for (ExecutableElement method : methods) { 159 classifyMethod(method); 160 } 161 if (errorReporter.errorCount() > startErrorCount) { 162 return false; 163 } 164 Multimap<String, PropertySetter> propertyNameToSetter; 165 if (propertyNameToPrefixedSetters.isEmpty()) { 166 propertyNameToSetter = propertyNameToUnprefixedSetters; 167 this.settersPrefixed = false; 168 } else if (propertyNameToUnprefixedSetters.isEmpty()) { 169 propertyNameToSetter = propertyNameToPrefixedSetters; 170 this.settersPrefixed = true; 171 } else { 172 errorReporter.reportError( 173 propertyNameToUnprefixedSetters.values().iterator().next().getSetter(), 174 "[%sSetNotSet] If any setter methods use the setFoo convention then all must", 175 autoWhat()); 176 return false; 177 } 178 for (String property : rewrittenPropertyTypes.keySet()) { 179 TypeMirror propertyType = rewrittenPropertyTypes.get(property); 180 boolean hasSetter = propertyNameToSetter.containsKey(property); 181 PropertyBuilder propertyBuilder = propertyNameToPropertyBuilder.get(property); 182 boolean hasBuilder = propertyBuilder != null; 183 if (hasBuilder) { 184 // If property bar of type Bar has a barBuilder() that returns BarBuilder, then it must 185 // be possible to make a BarBuilder from a Bar if either (1) the @AutoValue class has a 186 // toBuilder() or (2) there is also a setBar(Bar). Making BarBuilder from Bar is 187 // possible if Bar either has a toBuilder() method or BarBuilder has an addAll or putAll 188 // method that accepts a Bar argument. 189 boolean canMakeBarBuilder = 190 (propertyBuilder.getBuiltToBuilder() != null || propertyBuilder.getCopyAll() != null); 191 boolean needToMakeBarBuilder = (autoValueHasToBuilder || hasSetter); 192 if (needToMakeBarBuilder && !canMakeBarBuilder) { 193 errorReporter.reportError( 194 propertyBuilder.getPropertyBuilderMethod(), 195 "[AutoValueCantMakeBuilder] Property builder method returns %1$s but there is no" 196 + " way to make that type from %2$s: %2$s does not have a non-static" 197 + " toBuilder() method that returns %1$s, and %1$s does not have a method" 198 + " addAll or putAll that accepts an argument of type %2$s", 199 propertyBuilder.getBuilderTypeMirror(), 200 propertyType); 201 } 202 } else if (!hasSetter && !propertiesWithDefaults.contains(property)) { 203 // We have neither barBuilder() nor setBar(Bar), so we should complain. 204 String setterName = settersPrefixed ? prefixWithSet(property) : property; 205 errorReporter.reportError( 206 builderType, 207 "[%sBuilderMissingMethod] Expected a method with this signature: %s" 208 + " %s(%s), or a %sBuilder() method", 209 autoWhat(), 210 builderType.asType(), 211 setterName, 212 propertyType, 213 property); 214 } 215 } 216 return errorReporter.errorCount() == startErrorCount; 217 } 218 219 /** Classifies a method and update the state of this object based on what is found. */ classifyMethod(ExecutableElement method)220 private void classifyMethod(ExecutableElement method) { 221 switch (method.getParameters().size()) { 222 case 0: 223 classifyMethodNoArgs(method); 224 break; 225 case 1: 226 classifyMethodOneArg(method); 227 break; 228 default: 229 errorReporter.reportError( 230 method, "[%sBuilderArgs] Builder methods must have 0 or 1 parameters", autoWhat()); 231 } 232 } 233 234 /** 235 * Classifies a method given that it has no arguments. Currently a method with no arguments can be 236 * a {@code build()} method, meaning that its return type must be the {@code @AutoValue} class; it 237 * can be a getter, with the same signature as one of the property getters in the 238 * {@code @AutoValue} class; or it can be a property builder, like {@code 239 * ImmutableList.Builder<String> foosBuilder()} for the property defined by {@code 240 * ImmutableList<String> foos()} or {@code getFoos()}. 241 */ classifyMethodNoArgs(ExecutableElement method)242 private void classifyMethodNoArgs(ExecutableElement method) { 243 Optional<String> getterProperty = propertyForBuilderGetter(method); 244 if (getterProperty.isPresent()) { 245 classifyGetter(method, getterProperty.get()); 246 return; 247 } 248 249 String methodName = method.getSimpleName().toString(); 250 TypeMirror returnType = builderMethodReturnType(method); 251 252 if (methodName.endsWith("Builder")) { 253 String prefix = methodName.substring(0, methodName.length() - "Builder".length()); 254 String property = 255 rewrittenPropertyTypes.containsKey(prefix) 256 ? prefix 257 : rewrittenPropertyTypes.keySet().stream() 258 .filter(p -> PropertyNames.decapitalizeNormally(p).equals(prefix)) 259 .findFirst() 260 .orElse(null); 261 if (property != null) { 262 PropertyBuilderClassifier propertyBuilderClassifier = 263 new PropertyBuilderClassifier( 264 errorReporter, 265 typeUtils, 266 elementUtils, 267 this, 268 this::propertyIsNullable, 269 rewrittenPropertyTypes, 270 eclipseHack, 271 nullables); 272 Optional<PropertyBuilder> propertyBuilder = 273 propertyBuilderClassifier.makePropertyBuilder(method, property); 274 if (propertyBuilder.isPresent()) { 275 propertyNameToPropertyBuilder.put(property, propertyBuilder.get()); 276 } 277 return; 278 } 279 } 280 281 if (TYPE_EQUIVALENCE.equivalent(returnType, builtType)) { 282 buildMethods.add(method); 283 } else { 284 errorReporter.reportError( 285 method, 286 "[%1$sBuilderNoArg] Method without arguments should be a build method returning" 287 + " %2$s, or a getter method with the same name and type as %3$s," 288 + " or fooBuilder() where %4$s is %3$s", 289 // "where foo() or getFoo() is a method in..." or "where foo is a parameter of..." 290 autoWhat(), 291 builtType, 292 getterMustMatch(), 293 fooBuilderMustMatch()); 294 } 295 } 296 classifyGetter(ExecutableElement builderGetter, String propertyName)297 private void classifyGetter(ExecutableElement builderGetter, String propertyName) { 298 TypeMirror originalGetterType = rewrittenPropertyTypes.get(propertyName); 299 TypeMirror builderGetterType = builderMethodReturnType(builderGetter); 300 String builderGetterTypeString = TypeEncoder.encodeWithAnnotations(builderGetterType); 301 if (TYPE_EQUIVALENCE.equivalent(builderGetterType, originalGetterType)) { 302 builderGetters.put( 303 propertyName, 304 new BuilderSpec.PropertyGetter(builderGetter, builderGetterTypeString, null)); 305 return; 306 } 307 Optionalish optional = Optionalish.createIfOptional(builderGetterType); 308 if (optional != null) { 309 TypeMirror containedType = optional.getContainedType(typeUtils); 310 // If the original method is int getFoo() then we allow Optional<Integer> here. 311 // boxedOriginalType is Integer, and containedType is also Integer. 312 // We don't need any special code for OptionalInt because containedType will be int then. 313 TypeMirror boxedOriginalType = 314 originalGetterType.getKind().isPrimitive() 315 ? typeUtils.boxedClass(MoreTypes.asPrimitiveType(originalGetterType)).asType() 316 : null; 317 if (TYPE_EQUIVALENCE.equivalent(containedType, originalGetterType) 318 || TYPE_EQUIVALENCE.equivalent(containedType, boxedOriginalType)) { 319 builderGetters.put( 320 propertyName, 321 new BuilderSpec.PropertyGetter(builderGetter, builderGetterTypeString, optional)); 322 return; 323 } 324 } 325 errorReporter.reportError( 326 builderGetter, 327 "[AutoValueBuilderReturnType] Method matches a property of %1$s but has return type %2$s" 328 + " instead of %3$s or an Optional wrapping of %3$s", 329 builtType, 330 builderGetterType, 331 originalGetterType); 332 } 333 334 /** 335 * Classifies a method given that it has one argument. A method with one argument can be: 336 * 337 * <ul> 338 * <li>a setter, meaning that it looks like {@code foo(T)} or {@code setFoo(T)}, where the 339 * {@code AutoValue} class has a property called {@code foo} of type {@code T}; 340 * <li>a property builder with one argument, meaning it looks like {@code 341 * ImmutableSortedSet.Builder<V> foosBuilder(Comparator<V>)}, where the {@code AutoValue} 342 * class has a property called {@code foos} with a type whose builder can be made with an 343 * argument of the given type. 344 * </ul> 345 */ classifyMethodOneArg(ExecutableElement method)346 private void classifyMethodOneArg(ExecutableElement method) { 347 if (classifyPropertyBuilderOneArg(method)) { 348 return; 349 } 350 String methodName = method.getSimpleName().toString(); 351 ImmutableMap<String, E> propertyElements = propertyElements(); 352 String propertyName = null; 353 E propertyElement = propertyElements.get(methodName); 354 Multimap<String, PropertySetter> propertyNameToSetters = null; 355 if (propertyElement != null) { 356 propertyNameToSetters = propertyNameToUnprefixedSetters; 357 propertyName = methodName; 358 } else if (methodName.startsWith("set") && methodName.length() > 3) { 359 propertyNameToSetters = propertyNameToPrefixedSetters; 360 propertyName = PropertyNames.decapitalizeLikeJavaBeans(methodName.substring(3)); 361 propertyElement = propertyElements.get(propertyName); 362 if (propertyElement == null) { 363 // If our property is defined by a getter called getOAuth() then it is called "OAuth" 364 // because of JavaBeans rules. Therefore we want JavaBeans rules to be used for the setter 365 // too, so that you can write setOAuth(x). Meanwhile if the property is defined by a getter 366 // called oAuth() then it is called "oAuth", but you would still expect to be able to set it 367 // using setOAuth(x). Hence the second try using a decapitalize method without the quirky 368 // two-leading-capitals rule. 369 propertyName = PropertyNames.decapitalizeNormally(methodName.substring(3)); 370 propertyElement = propertyElements.get(propertyName); 371 } 372 } else { 373 // We might also have an unprefixed setter, so the getter is called OAuth() or getOAuth() and 374 // the setter is called oAuth(x), where again JavaBeans rules imply that it should be called 375 // OAuth(x). Iterating over the properties here is a bit clunky but this case should be 376 // unusual. 377 propertyNameToSetters = propertyNameToUnprefixedSetters; 378 for (Map.Entry<String, E> entry : propertyElements.entrySet()) { 379 if (methodName.equals(PropertyNames.decapitalizeNormally(entry.getKey()))) { 380 propertyName = entry.getKey(); 381 propertyElement = entry.getValue(); 382 break; 383 } 384 } 385 } 386 if (propertyElement == null || propertyNameToSetters == null) { 387 // The second disjunct isn't needed but convinces control-flow checkers that 388 // propertyNameToSetters can't be null when we call put on it below. 389 errorReporter.reportError( 390 method, 391 "[%sBuilderWhatProp] Method %s does not correspond to %s", 392 autoWhat(), 393 methodName, 394 getterMustMatch()); 395 checkForFailedJavaBean(method); 396 return; 397 } 398 Optional<Copier> function = getSetterFunction(propertyElement, method); 399 if (function.isPresent()) { 400 DeclaredType builderTypeMirror = MoreTypes.asDeclared(builderType.asType()); 401 ExecutableType methodMirror = 402 MoreTypes.asExecutable(typeUtils.asMemberOf(builderTypeMirror, method)); 403 TypeMirror returnType = methodMirror.getReturnType(); 404 if (typeUtils.isSubtype(builderType.asType(), returnType) 405 && !MoreTypes.isTypeOf(Object.class, returnType)) { 406 if (nullableAnnotationFor(method, returnType).isPresent()) { 407 errorReporter. 408 reportWarning( 409 method, 410 "[%sBuilderSetterNullable] Setter methods always return the Builder so @Nullable" 411 + " is not appropriate", autoWhat()); 412 } 413 // We allow the return type to be a supertype (other than Object), to support step builders. 414 TypeMirror parameterType = Iterables.getOnlyElement(methodMirror.getParameterTypes()); 415 propertyNameToSetters.put( 416 propertyName, new PropertySetter(method, parameterType, function.get())); 417 } else { 418 errorReporter.reportError( 419 method, 420 "[%sBuilderRet] Setter methods must return %s or a supertype", 421 autoWhat(), 422 builderType.asType()); 423 } 424 } 425 } 426 427 /** 428 * Classifies a method given that it has one argument and is a property builder with a parameter, 429 * like {@code ImmutableSortedSet.Builder<String> foosBuilder(Comparator<String>)}. 430 * 431 * @param method A method to classify 432 * @return true if method has been classified successfully 433 */ classifyPropertyBuilderOneArg(ExecutableElement method)434 private boolean classifyPropertyBuilderOneArg(ExecutableElement method) { 435 String methodName = method.getSimpleName().toString(); 436 if (!methodName.endsWith("Builder")) { 437 return false; 438 } 439 String property = methodName.substring(0, methodName.length() - "Builder".length()); 440 if (!rewrittenPropertyTypes.containsKey(property)) { 441 return false; 442 } 443 PropertyBuilderClassifier propertyBuilderClassifier = 444 new PropertyBuilderClassifier( 445 errorReporter, 446 typeUtils, 447 elementUtils, 448 this, 449 this::propertyIsNullable, 450 rewrittenPropertyTypes, 451 eclipseHack, 452 nullables); 453 Optional<PropertyBuilder> maybePropertyBuilder = 454 propertyBuilderClassifier.makePropertyBuilder(method, property); 455 maybePropertyBuilder.ifPresent( 456 propertyBuilder -> propertyNameToPropertyBuilder.put(property, propertyBuilder)); 457 return maybePropertyBuilder.isPresent(); 458 } 459 460 /** 461 * Returns an {@code Optional} describing how to convert a value from the setter's parameter type 462 * to the getter's return type, or {@code Optional.empty()} if the conversion isn't possible. An 463 * error will have been reported in the latter case. We can convert if they are already the same 464 * type, when the returned function will be the identity; or if the setter type can be copied 465 * using a method like {@code ImmutableList.copyOf} or {@code Optional.of}, when the returned 466 * function will be something like {@code s -> "Optional.of(" + s + ")"}. 467 */ getSetterFunction(E propertyElement, ExecutableElement setter)468 private Optional<Copier> getSetterFunction(E propertyElement, ExecutableElement setter) { 469 VariableElement parameterElement = Iterables.getOnlyElement(setter.getParameters()); 470 boolean nullableParameter = 471 nullableAnnotationFor(parameterElement, parameterElement.asType()).isPresent(); 472 String property = propertyElements().inverse().get(propertyElement); 473 TypeMirror targetType = rewrittenPropertyTypes.get(property); 474 ExecutableType finalSetter = 475 MoreTypes.asExecutable( 476 typeUtils.asMemberOf(MoreTypes.asDeclared(builderType.asType()), setter)); 477 TypeMirror parameterType = finalSetter.getParameterTypes().get(0); 478 // Two types are assignable to each other if they are the same type, or if one is primitive and 479 // the other is the corresponding boxed type. There might be other cases where this is true, but 480 // we're likely to want to accept those too. 481 if (typeUtils.isAssignable(parameterType, targetType) 482 && typeUtils.isAssignable(targetType, parameterType)) { 483 if (nullableParameter) { 484 boolean nullableProperty = 485 nullableAnnotationFor(propertyElement, originalPropertyType(propertyElement)) 486 .isPresent(); 487 if (!nullableProperty) { 488 errorReporter.reportError( 489 setter, 490 "[%sNullNotNull] Parameter of setter method is @Nullable but %s is not", 491 autoWhat(), 492 propertyString(propertyElement)); 493 return Optional.empty(); 494 } 495 } 496 return Optional.of(Copier.IDENTITY); 497 } 498 499 // Parameter type is not equal to property type, but might be convertible with copyOf. 500 ImmutableList<ExecutableElement> copyOfMethods = copyOfMethods(targetType, nullableParameter); 501 if (!copyOfMethods.isEmpty()) { 502 return getConvertingSetterFunction(copyOfMethods, propertyElement, setter, parameterType); 503 } 504 errorReporter.reportError( 505 setter, 506 "[%sGetVsSet] Parameter type %s of setter method should be %s to match %s", 507 autoWhat(), 508 parameterType, 509 targetType, 510 propertyString(propertyElement)); 511 return Optional.empty(); 512 } 513 514 /** 515 * Returns an {@code Optional} describing how to convert a value from the setter's parameter type 516 * to the getter's return type using one of the given methods, or {@code Optional.empty()} if the 517 * conversion isn't possible. An error will have been reported in the latter case. 518 */ getConvertingSetterFunction( ImmutableList<ExecutableElement> copyOfMethods, E propertyElement, ExecutableElement setter, TypeMirror parameterType)519 private Optional<Copier> getConvertingSetterFunction( 520 ImmutableList<ExecutableElement> copyOfMethods, 521 E propertyElement, 522 ExecutableElement setter, 523 TypeMirror parameterType) { 524 String property = propertyElements().inverse().get(propertyElement); 525 DeclaredType targetType = MoreTypes.asDeclared(rewrittenPropertyTypes.get(property)); 526 for (ExecutableElement copyOfMethod : copyOfMethods) { 527 Optional<Copier> function = 528 getConvertingSetterFunction(copyOfMethod, targetType, parameterType); 529 if (function.isPresent()) { 530 return function; 531 } 532 } 533 String targetTypeSimpleName = targetType.asElement().getSimpleName().toString(); 534 errorReporter.reportError( 535 setter, 536 "[%sGetVsSetOrConvert] Parameter type %s of setter method should be %s to match %s, or it" 537 + " should be a type that can be passed to %s.%s to produce %s", 538 autoWhat(), 539 parameterType, 540 targetType, 541 propertyString(propertyElement), 542 targetTypeSimpleName, 543 copyOfMethods.get(0).getSimpleName(), 544 targetType); 545 return Optional.empty(); 546 } 547 548 /** 549 * Returns an {@code Optional} containing a function to use {@code copyOfMethod} to copy the 550 * {@code parameterType} to the {@code targetType}, or {@code Optional.empty()} if the method 551 * can't be used. For example, we might have a property of type {@code ImmutableSet<T>} and our 552 * setter has a parameter of type {@code Set<? extends T>}. Can we use {@code ImmutableSet<E> 553 * ImmutableSet.copyOf(Collection<? extends E>)} to set the property? What about {@code 554 * ImmutableSet<E> ImmutableSet.copyOf(E[])}? 555 * 556 * <p>The example here is deliberately complicated, in that it has a type parameter of its own, 557 * presumably because the {@code @AutoValue} class is {@code Foo<T>}. One subtle point is that the 558 * builder will then be {@code Builder<T>} where this {@code T} is a <i>different</i> type 559 * variable. However, we've used {@link TypeVariables} to ensure that the {@code T} in {@code 560 * ImmutableSet<T>} is actually the one from {@code Builder<T>} instead of the original one from 561 * {@code Foo<T>}.} 562 * 563 * @param copyOfMethod the candidate method to do the copy, {@code 564 * ImmutableSet.copyOf(Collection<? extends E>)} or {@code ImmutableSet.copyOf(E[])} in the 565 * examples. 566 * @param targetType the type of the property to be set, {@code ImmutableSet<T>} in the example. 567 * @param parameterType the type of the setter parameter, {@code Set<? extends T>} in the example. 568 * @return a function that maps a string parameter to a method call using that parameter. For 569 * example it might map {@code foo} to {@code ImmutableList.copyOf(foo)}. 570 */ getConvertingSetterFunction( ExecutableElement copyOfMethod, DeclaredType targetType, TypeMirror parameterType)571 private Optional<Copier> getConvertingSetterFunction( 572 ExecutableElement copyOfMethod, DeclaredType targetType, TypeMirror parameterType) { 573 // We have a parameter type, for example Set<? extends T>, and we want to know if it can be 574 // passed to the given copyOf method, which might for example be one of these methods from 575 // ImmutableSet: 576 // public static <E> ImmutableSet<E> copyOf(Collection<? extends E> elements) 577 // public static <E> ImmutableSet<E> copyOf(E[] elements) 578 // Additionally, if it can indeed be passed to the method, we want to know whether the result 579 // (here ImmutableSet<? extends T>) is compatible with the property to be set. 580 // We can't use Types.asMemberOf to do the substitution for us, because the methods in question 581 // are static. So even if our target type is ImmutableSet<String>, if we ask what the type of 582 // copyOf is in ImmutableSet<String> it will still tell us <T> Optional<T> (T). 583 // Instead, we do the variable substitutions ourselves. 584 if (TypeVariables.canAssignStaticMethodResult( 585 copyOfMethod, parameterType, targetType, typeUtils)) { 586 String method = TypeEncoder.encodeRaw(targetType) + "." + copyOfMethod.getSimpleName(); 587 Function<String, String> callMethod = s -> method + "(" + s + ")"; 588 // This is a big old hack. We guess that the method can accept a null parameter if it has 589 // "Nullable" in the name, which java.util.Optional.ofNullable and 590 // com.google.common.base.Optional.fromNullable do. 591 Copier copier = 592 method.contains("Nullable") 593 ? Copier.acceptingNull(callMethod) 594 : Copier.notAcceptingNull(callMethod); 595 return Optional.of(copier); 596 } 597 return Optional.empty(); 598 } 599 600 /** 601 * Returns {@code copyOf} methods from the given type. These are static methods with a single 602 * parameter, called {@code copyOf} or {@code copyOfSorted} for Guava collection types, and called 603 * {@code of} or {@code ofNullable} for {@code Optional}. All of Guava's concrete immutable 604 * collection types have at least one such method, but we will also accept other classes with an 605 * appropriate {@code copyOf} method, such as {@link java.util.EnumSet}. 606 */ copyOfMethods( TypeMirror targetType, boolean nullableParameter)607 private ImmutableList<ExecutableElement> copyOfMethods( 608 TypeMirror targetType, boolean nullableParameter) { 609 if (!targetType.getKind().equals(TypeKind.DECLARED)) { 610 return ImmutableList.of(); 611 } 612 ImmutableSet<String> copyOfNames; 613 Optionalish optionalish = Optionalish.createIfOptional(targetType); 614 if (optionalish == null) { 615 copyOfNames = ImmutableSet.of("copyOfSorted", "copyOf"); 616 } else { 617 copyOfNames = ImmutableSet.of(nullableParameter ? optionalish.ofNullable() : "of"); 618 } 619 TypeElement targetTypeElement = MoreElements.asType(typeUtils.asElement(targetType)); 620 ImmutableList.Builder<ExecutableElement> copyOfMethods = ImmutableList.builder(); 621 for (String copyOfName : copyOfNames) { 622 for (ExecutableElement method : 623 ElementFilter.methodsIn(targetTypeElement.getEnclosedElements())) { 624 if (method.getSimpleName().contentEquals(copyOfName) 625 && method.getParameters().size() == 1 626 && method.getModifiers().contains(Modifier.STATIC)) { 627 copyOfMethods.add(method); 628 } 629 } 630 } 631 return copyOfMethods.build(); 632 } 633 634 /** 635 * Returns the return type of the given method from the builder. This should be the final type of 636 * the method when any bound type variables are substituted. Consider this example: 637 * 638 * <pre>{@code 639 * abstract static class ParentBuilder<B extends ParentBuilder> { 640 * B setFoo(String s); 641 * } 642 * abstract static class ChildBuilder extends ParentBuilder<ChildBuilder> { 643 * ... 644 * } 645 * }</pre> 646 * 647 * If the builder is {@code ChildBuilder} then the return type of {@code setFoo} is also {@code 648 * ChildBuilder}, and not {@code B} as its {@code getReturnType()} method would claim. 649 * 650 * <p>If the caller is in a version of Eclipse with <a 651 * href="https://bugs.eclipse.org/bugs/show_bug.cgi?id=382590">this bug</a> then the {@code 652 * asMemberOf} call will fail if the method is inherited from an interface. We work around that 653 * for methods in the {@code @AutoValue} class using {@link EclipseHack#methodReturnTypes} but we 654 * don't try to do so here because it should be much less likely. You might need to change {@code 655 * ParentBuilder} from an interface to an abstract class to make it work, but you'll often need to 656 * do that anyway. 657 */ builderMethodReturnType(ExecutableElement builderMethod)658 TypeMirror builderMethodReturnType(ExecutableElement builderMethod) { 659 DeclaredType builderTypeMirror = MoreTypes.asDeclared(builderType.asType()); 660 TypeMirror methodMirror; 661 try { 662 methodMirror = typeUtils.asMemberOf(builderTypeMirror, builderMethod); 663 } catch (IllegalArgumentException e) { 664 // Presumably we've hit the Eclipse bug cited. 665 return builderMethod.getReturnType(); 666 } 667 return MoreTypes.asExecutable(methodMirror).getReturnType(); 668 } 669 prefixWithSet(String propertyName)670 private static String prefixWithSet(String propertyName) { 671 // This is not internationalizationally correct, but it corresponds to what 672 // Introspector.decapitalize does. 673 return "set" + Character.toUpperCase(propertyName.charAt(0)) + propertyName.substring(1); 674 } 675 676 /** 677 * True if the given property is nullable, either because its type has a {@code @Nullable} type 678 * annotation, or because its getter method has a {@code @Nullable} method annotation. 679 */ propertyIsNullable(String property)680 private boolean propertyIsNullable(String property) { 681 E propertyElement = propertyElements().get(property); 682 return Stream.of(propertyElement, originalPropertyType(propertyElement)) 683 .flatMap(ac -> ac.getAnnotationMirrors().stream()) 684 .map(a -> a.getAnnotationType().asElement().getSimpleName()) 685 .anyMatch(n -> n.contentEquals("Nullable")); 686 } 687 688 /** 689 * Returns a map from property names to the corresponding source program elements. For AutoValue, 690 * these elements are the abstract getter methods in the {@code @AutoValue} class. For 691 * AutoBuilder, they are the parameters of the constructor or method that the generated builder 692 * will call. 693 */ propertyElements()694 abstract ImmutableBiMap<String, E> propertyElements(); 695 696 /** 697 * Returns the property type as it appears on the original source program element. This can be 698 * different from the type stored in {@link #rewrittenPropertyTypes} since that one will refer to 699 * type variables in the builder rather than in the original class. Also, {@link 700 * #rewrittenPropertyTypes} will not have type annotations even if they were present on the 701 * original element, so {@code originalPropertyType} is the right thing to use for those. 702 */ originalPropertyType(E propertyElement)703 abstract TypeMirror originalPropertyType(E propertyElement); 704 705 /** 706 * A string identifying the given property element, which is a method for AutoValue or a parameter 707 * for AutoBuilder. 708 */ propertyString(E propertyElement)709 abstract String propertyString(E propertyElement); 710 711 /** 712 * Returns the name of the property that the given no-arg builder method queries, if 713 * any. For example, if your {@code @AutoValue} class has a method {@code abstract String 714 * getBar()} then an abstract method in its builder with the same signature will query the {@code 715 * bar} property. 716 */ propertyForBuilderGetter(ExecutableElement method)717 abstract Optional<String> propertyForBuilderGetter(ExecutableElement method); 718 719 /** 720 * Checks for failed JavaBean usage when a method that looks like a setter doesn't actually match 721 * anything, and emits a compiler Note if detected. A frequent source of problems is where the 722 * JavaBeans conventions have been followed for most but not all getters. Then AutoValue considers 723 * that they haven't been followed at all, so you might have a property called getFoo where you 724 * thought it was called just foo, and you might not understand why your setter called setFoo is 725 * rejected (it would have to be called setGetFoo). 726 * 727 * <p>This is not relevant for AutoBuilder, which uses parameter names rather than getters. The 728 * parameter names are unambiguously the same as the property names. 729 */ checkForFailedJavaBean(ExecutableElement rejectedSetter)730 abstract void checkForFailedJavaBean(ExecutableElement rejectedSetter); 731 732 /** 733 * A string describing what sort of Auto this is, {@code "AutoValue"} or {@code "AutoBuilder"}. 734 */ autoWhat()735 abstract String autoWhat(); 736 737 /** 738 * A string describing what a builder getter must match: a property method for AutoValue, a 739 * parameter for AutoBuilder. 740 */ getterMustMatch()741 abstract String getterMustMatch(); 742 743 /** 744 * A string describing what a property builder for property {@code foo} must match, {@code foo() 745 * or getFoo()} for AutoValue, {@code foo} for AutoBuilder. 746 */ fooBuilderMustMatch()747 abstract String fooBuilderMustMatch(); 748 } 749