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