1 /* 2 * Copyright 2012 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.common.MoreStreams.toImmutableList; 20 import static com.google.auto.value.processor.ClassNames.AUTO_VALUE_NAME; 21 import static com.google.common.base.Preconditions.checkState; 22 import static com.google.common.collect.Sets.difference; 23 import static com.google.common.collect.Sets.intersection; 24 import static java.util.Comparator.naturalOrder; 25 import static java.util.stream.Collectors.joining; 26 27 import com.google.auto.service.AutoService; 28 import com.google.auto.value.extension.AutoValueExtension; 29 import com.google.common.annotations.VisibleForTesting; 30 import com.google.common.base.Strings; 31 import com.google.common.base.Throwables; 32 import com.google.common.collect.ImmutableBiMap; 33 import com.google.common.collect.ImmutableList; 34 import com.google.common.collect.ImmutableListMultimap; 35 import com.google.common.collect.ImmutableMap; 36 import com.google.common.collect.ImmutableSet; 37 import java.lang.annotation.Annotation; 38 import java.util.ArrayList; 39 import java.util.Collections; 40 import java.util.HashSet; 41 import java.util.List; 42 import java.util.Optional; 43 import java.util.ServiceConfigurationError; 44 import java.util.Set; 45 import javax.annotation.processing.ProcessingEnvironment; 46 import javax.annotation.processing.Processor; 47 import javax.annotation.processing.SupportedAnnotationTypes; 48 import javax.lang.model.element.AnnotationMirror; 49 import javax.lang.model.element.ExecutableElement; 50 import javax.lang.model.element.TypeElement; 51 import javax.lang.model.type.TypeKind; 52 import javax.lang.model.type.TypeMirror; 53 import net.ltgt.gradle.incap.IncrementalAnnotationProcessor; 54 import net.ltgt.gradle.incap.IncrementalAnnotationProcessorType; 55 56 /** 57 * Javac annotation processor (compiler plugin) for value types; user code never references this 58 * class. 59 * 60 * @see <a href="https://github.com/google/auto/tree/main/value">AutoValue User's Guide</a> 61 * @author Éamonn McManus 62 */ 63 @AutoService(Processor.class) 64 @SupportedAnnotationTypes(AUTO_VALUE_NAME) 65 @IncrementalAnnotationProcessor(IncrementalAnnotationProcessorType.DYNAMIC) 66 public class AutoValueProcessor extends AutoValueishProcessor { 67 static final String OMIT_IDENTIFIERS_OPTION = "com.google.auto.value.OmitIdentifiers"; 68 69 // We moved MemoizeExtension to a different package, which had an unexpected effect: 70 // now if an old version of AutoValue is in the class path, ServiceLoader can pick up both the 71 // old and the new versions of MemoizeExtension. So we exclude the old version if we see it. 72 // The new version will be bundled with this processor so we should always find it. 73 private static final String OLD_MEMOIZE_EXTENSION = 74 "com.google.auto.value.extension.memoized.MemoizeExtension"; 75 AutoValueProcessor()76 public AutoValueProcessor() { 77 this(AutoValueProcessor.class.getClassLoader()); 78 } 79 80 @VisibleForTesting AutoValueProcessor(ClassLoader loaderForExtensions)81 AutoValueProcessor(ClassLoader loaderForExtensions) { 82 this(ImmutableList.of(), loaderForExtensions); 83 } 84 85 @VisibleForTesting AutoValueProcessor(Iterable<? extends AutoValueExtension> testExtensions)86 public AutoValueProcessor(Iterable<? extends AutoValueExtension> testExtensions) { 87 this(testExtensions, null); 88 } 89 AutoValueProcessor( Iterable<? extends AutoValueExtension> testExtensions, ClassLoader loaderForExtensions)90 private AutoValueProcessor( 91 Iterable<? extends AutoValueExtension> testExtensions, ClassLoader loaderForExtensions) { 92 super(AUTO_VALUE_NAME, /* appliesToInterfaces= */ false); 93 this.extensions = ImmutableList.copyOf(testExtensions); 94 this.loaderForExtensions = loaderForExtensions; 95 } 96 97 // Depending on how this AutoValueProcessor was constructed, we might already have a list of 98 // extensions when init() is run, or, if `loaderForExtensions` is not null, it is a ClassLoader 99 // that will be used to get the list using the ServiceLoader API. 100 private ImmutableList<AutoValueExtension> extensions; 101 private final ClassLoader loaderForExtensions; 102 103 @VisibleForTesting extensionsFromLoader(ClassLoader loader)104 static ImmutableList<AutoValueExtension> extensionsFromLoader(ClassLoader loader) { 105 return SimpleServiceLoader.load(AutoValueExtension.class, loader).stream() 106 .filter(ext -> !ext.getClass().getName().equals(OLD_MEMOIZE_EXTENSION)) 107 .collect(toImmutableList()); 108 } 109 110 @Override init(ProcessingEnvironment processingEnv)111 public synchronized void init(ProcessingEnvironment processingEnv) { 112 super.init(processingEnv); 113 114 if (loaderForExtensions != null) { 115 checkState(extensions.isEmpty()); 116 try { 117 extensions = extensionsFromLoader(loaderForExtensions); 118 } catch (RuntimeException | Error e) { 119 String explain = 120 (e instanceof ServiceConfigurationError) 121 ? " This may be due to a corrupt jar file in the compiler's classpath." 122 : ""; 123 errorReporter() 124 .reportWarning( 125 null, 126 "[AutoValueExtensionsException] An exception occurred while looking for AutoValue" 127 + " extensions. No extensions will function.%s\n%s", 128 explain, 129 Throwables.getStackTraceAsString(e)); 130 extensions = ImmutableList.of(); 131 } 132 } 133 } 134 135 @Override getSupportedOptions()136 public ImmutableSet<String> getSupportedOptions() { 137 ImmutableSet.Builder<String> builder = ImmutableSet.builder(); 138 AutoValueExtension.IncrementalExtensionType incrementalType = 139 extensions.stream() 140 .map(e -> e.incrementalType(processingEnv)) 141 .min(naturalOrder()) 142 .orElse(AutoValueExtension.IncrementalExtensionType.ISOLATING); 143 builder 144 .add(OMIT_IDENTIFIERS_OPTION) 145 .add(Nullables.NULLABLE_OPTION) 146 .addAll(optionsFor(incrementalType)); 147 for (AutoValueExtension extension : extensions) { 148 builder.addAll(extension.getSupportedOptions()); 149 } 150 return builder.build(); 151 } 152 optionsFor( AutoValueExtension.IncrementalExtensionType incrementalType)153 private static ImmutableSet<String> optionsFor( 154 AutoValueExtension.IncrementalExtensionType incrementalType) { 155 switch (incrementalType) { 156 case ISOLATING: 157 return ImmutableSet.of(IncrementalAnnotationProcessorType.ISOLATING.getProcessorOption()); 158 case AGGREGATING: 159 return ImmutableSet.of(IncrementalAnnotationProcessorType.AGGREGATING.getProcessorOption()); 160 case UNKNOWN: 161 return ImmutableSet.of(); 162 } 163 throw new AssertionError(incrementalType); 164 } 165 generatedSubclassName(TypeElement type, int depth)166 static String generatedSubclassName(TypeElement type, int depth) { 167 return generatedClassName(type, Strings.repeat("$", depth) + "AutoValue_"); 168 } 169 170 @Override processType(TypeElement type)171 void processType(TypeElement type) { 172 if (ancestorIsAutoValue(type)) { 173 errorReporter() 174 .abortWithError(type, "[AutoValueExtend] One @AutoValue class may not extend another"); 175 } 176 if (implementsAnnotation(type)) { 177 errorReporter() 178 .abortWithError( 179 type, 180 "[AutoValueImplAnnotation] @AutoValue may not be used to implement an annotation" 181 + " interface; try using @AutoAnnotation or @AutoBuilder instead"); 182 } 183 184 // We are going to classify the methods of the @AutoValue class into several categories. 185 // This covers the methods in the class itself and the ones it inherits from supertypes. 186 // First, the only concrete (non-abstract) methods we are interested in are overrides of 187 // Object methods (equals, hashCode, toString), which signal that we should not generate 188 // an implementation of those methods. 189 // Then, each abstract method is one of the following: 190 // (1) A property getter, like "abstract String foo()" or "abstract String getFoo()". 191 // (2) A toBuilder() method, which is any abstract no-arg method returning the Builder for 192 // this @AutoValue class. 193 // (3) An abstract method that will be consumed by an extension, such as 194 // Parcelable.describeContents() or Parcelable.writeToParcel(Parcel, int). 195 // The describeContents() example shows a quirk here: initially we will identify it as a 196 // property, which means that we need to reconstruct the list of properties after allowing 197 // extensions to consume abstract methods. 198 // If there are abstract methods that don't fit any of the categories above, that is an error 199 // which we signal explicitly to avoid confusion. 200 201 ImmutableSet<ExecutableElement> methods = 202 getLocalAndInheritedMethods( 203 type, processingEnv.getTypeUtils(), processingEnv.getElementUtils()); 204 ImmutableSet<ExecutableElement> abstractMethods = abstractMethodsIn(methods); 205 206 BuilderSpec builderSpec = new BuilderSpec(type, processingEnv, errorReporter()); 207 Optional<BuilderSpec.Builder> builder = builderSpec.getBuilder(); 208 ImmutableSet<ExecutableElement> toBuilderMethods; 209 if (builder.isPresent()) { 210 toBuilderMethods = builder.get().toBuilderMethods(typeUtils(), type, abstractMethods); 211 } else { 212 toBuilderMethods = ImmutableSet.of(); 213 } 214 215 ImmutableMap<ExecutableElement, TypeMirror> propertyMethodsAndTypes = 216 propertyMethodsIn(immutableSetDifference(abstractMethods, toBuilderMethods), type); 217 ImmutableMap<String, ExecutableElement> properties = 218 propertyNameToMethodMap(propertyMethodsAndTypes.keySet()); 219 220 ExtensionContext context = 221 new ExtensionContext( 222 processingEnv, type, properties, propertyMethodsAndTypes, abstractMethods); 223 ImmutableList<AutoValueExtension> applicableExtensions = applicableExtensions(type, context); 224 ImmutableSet<ExecutableElement> consumedMethods = 225 methodsConsumedByExtensions( 226 type, applicableExtensions, context, abstractMethods, properties); 227 228 if (!consumedMethods.isEmpty()) { 229 ImmutableSet<ExecutableElement> allAbstractMethods = abstractMethods; 230 abstractMethods = immutableSetDifference(abstractMethods, consumedMethods); 231 toBuilderMethods = immutableSetDifference(toBuilderMethods, consumedMethods); 232 propertyMethodsAndTypes = 233 propertyMethodsIn(immutableSetDifference(abstractMethods, toBuilderMethods), type); 234 properties = propertyNameToMethodMap(propertyMethodsAndTypes.keySet()); 235 context = 236 new ExtensionContext( 237 processingEnv, type, properties, propertyMethodsAndTypes, allAbstractMethods); 238 } 239 240 ImmutableSet<ExecutableElement> propertyMethods = propertyMethodsAndTypes.keySet(); 241 boolean extensionsPresent = !applicableExtensions.isEmpty(); 242 validateMethods(type, abstractMethods, toBuilderMethods, propertyMethods, extensionsPresent); 243 244 String finalSubclass = TypeSimplifier.simpleNameOf(generatedSubclassName(type, 0)); 245 AutoValueTemplateVars vars = new AutoValueTemplateVars(); 246 vars.identifiers = !processingEnv.getOptions().containsKey(OMIT_IDENTIFIERS_OPTION); 247 Nullables nullables = Nullables.fromMethods(processingEnv, methods); 248 defineSharedVarsForType(type, methods, nullables, vars); 249 defineVarsForType( 250 type, 251 vars, 252 toBuilderMethods, 253 propertyMethodsAndTypes, 254 builder, 255 nullables); 256 vars.builtType = vars.origClass + vars.actualTypes; 257 vars.build = "new " + finalSubclass + vars.actualTypes; 258 259 // If we've encountered problems then we might end up invoking extensions with inconsistent 260 // state. Anyway we probably don't want to generate code which is likely to provoke further 261 // compile errors to add to the ones we've already seen. 262 errorReporter().abortIfAnyError(); 263 264 GwtCompatibility gwtCompatibility = new GwtCompatibility(type); 265 vars.gwtCompatibleAnnotation = gwtCompatibility.gwtCompatibleAnnotationString(); 266 267 builder.ifPresent(context::setBuilderContext); 268 int subclassDepth = writeExtensions(type, context, applicableExtensions); 269 String subclass = generatedSubclassName(type, subclassDepth); 270 vars.subclass = TypeSimplifier.simpleNameOf(subclass); 271 vars.isFinal = (subclassDepth == 0); 272 vars.modifiers = vars.isFinal ? "final " : "abstract "; 273 274 String text = vars.toText(); 275 text = TypeEncoder.decode(text, processingEnv, vars.pkg, type.asType()); 276 text = Reformatter.fixup(text); 277 writeSourceFile(subclass, text, type); 278 GwtSerialization gwtSerialization = new GwtSerialization(gwtCompatibility, processingEnv, type); 279 gwtSerialization.maybeWriteGwtSerializer(vars, finalSubclass); 280 } 281 282 // Invokes each of the given extensions to generate its subclass, and returns the number of 283 // hierarchy classes that extensions generated. This number is then the number of $ characters 284 // that should precede the name of the AutoValue implementation class. 285 // Assume the @AutoValue class is com.example.Foo.Bar. Then if there are no 286 // extensions the returned value will be 0, so the AutoValue implementation will be 287 // com.example.AutoValue_Foo_Bar. If there is one extension, it will be asked to 288 // generate AutoValue_Foo_Bar with parent $AutoValue_Foo_Bar. If it does so (returns 289 // non-null) then the returned value will be 1, so the AutoValue implementation will be 290 // com.example.$AutoValue_Foo_Bar. Otherwise, the returned value will still be 0. Likewise, 291 // if there is a second extension and both extensions return non-null, the first one will 292 // generate AutoValue_Foo_Bar with parent $AutoValue_Foo_Bar, the second will generate 293 // $AutoValue_Foo_Bar with parent $$AutoValue_Foo_Bar, and the returned value will be 2 for 294 // com.example.$$AutoValue_Foo_Bar. writeExtensions( TypeElement type, ExtensionContext context, ImmutableList<AutoValueExtension> applicableExtensions)295 private int writeExtensions( 296 TypeElement type, 297 ExtensionContext context, 298 ImmutableList<AutoValueExtension> applicableExtensions) { 299 int writtenSoFar = 0; 300 for (AutoValueExtension extension : applicableExtensions) { 301 String parentFqName = generatedSubclassName(type, writtenSoFar + 1); 302 String parentSimpleName = TypeSimplifier.simpleNameOf(parentFqName); 303 String classFqName = generatedSubclassName(type, writtenSoFar); 304 String classSimpleName = TypeSimplifier.simpleNameOf(classFqName); 305 boolean isFinal = (writtenSoFar == 0); 306 String source = extension.generateClass(context, classSimpleName, parentSimpleName, isFinal); 307 if (source != null) { 308 source = Reformatter.fixup(source); 309 writeSourceFile(classFqName, source, type); 310 writtenSoFar++; 311 } 312 } 313 return writtenSoFar; 314 } 315 applicableExtensions( TypeElement type, ExtensionContext context)316 private ImmutableList<AutoValueExtension> applicableExtensions( 317 TypeElement type, ExtensionContext context) { 318 List<AutoValueExtension> applicableExtensions = new ArrayList<>(); 319 List<AutoValueExtension> finalExtensions = new ArrayList<>(); 320 for (AutoValueExtension extension : extensions) { 321 if (extension.applicable(context)) { 322 if (extension.mustBeFinal(context)) { 323 finalExtensions.add(extension); 324 } else { 325 applicableExtensions.add(extension); 326 } 327 } 328 } 329 switch (finalExtensions.size()) { 330 case 0: 331 break; 332 case 1: 333 applicableExtensions.add(0, finalExtensions.get(0)); 334 break; 335 default: 336 errorReporter() 337 .reportError( 338 type, 339 "[AutoValueMultiFinal] More than one extension wants to generate the final class:" 340 + " %s", 341 finalExtensions.stream().map(this::extensionName).collect(joining(", "))); 342 break; 343 } 344 return ImmutableList.copyOf(applicableExtensions); 345 } 346 methodsConsumedByExtensions( TypeElement type, ImmutableList<AutoValueExtension> applicableExtensions, ExtensionContext context, ImmutableSet<ExecutableElement> abstractMethods, ImmutableMap<String, ExecutableElement> properties)347 private ImmutableSet<ExecutableElement> methodsConsumedByExtensions( 348 TypeElement type, 349 ImmutableList<AutoValueExtension> applicableExtensions, 350 ExtensionContext context, 351 ImmutableSet<ExecutableElement> abstractMethods, 352 ImmutableMap<String, ExecutableElement> properties) { 353 Set<ExecutableElement> consumed = new HashSet<>(); 354 for (AutoValueExtension extension : applicableExtensions) { 355 Set<ExecutableElement> consumedHere = new HashSet<>(); 356 for (String consumedProperty : extension.consumeProperties(context)) { 357 ExecutableElement propertyMethod = properties.get(consumedProperty); 358 if (propertyMethod == null) { 359 errorReporter() 360 .reportError( 361 type, 362 "[AutoValueConsumeNonexist] Extension %s wants to consume a property that does" 363 + " not exist: %s", 364 extensionName(extension), 365 consumedProperty); 366 } else { 367 consumedHere.add(propertyMethod); 368 } 369 } 370 for (ExecutableElement consumedMethod : extension.consumeMethods(context)) { 371 if (!abstractMethods.contains(consumedMethod)) { 372 errorReporter() 373 .reportError( 374 type, 375 "[AutoValueConsumeNotAbstract] Extension %s wants to consume a method that is" 376 + " not one of the abstract methods in this class: %s", 377 extensionName(extension), 378 consumedMethod); 379 } else { 380 consumedHere.add(consumedMethod); 381 } 382 } 383 for (ExecutableElement repeat : intersection(consumed, consumedHere)) { 384 errorReporter() 385 .reportError( 386 repeat, 387 "[AutoValueMultiConsume] Extension %s wants to consume a method that was already" 388 + " consumed by another extension", 389 extensionName(extension)); 390 } 391 consumed.addAll(consumedHere); 392 } 393 return ImmutableSet.copyOf(consumed); 394 } 395 validateMethods( TypeElement type, ImmutableSet<ExecutableElement> abstractMethods, ImmutableSet<ExecutableElement> toBuilderMethods, ImmutableSet<ExecutableElement> propertyMethods, boolean extensionsPresent)396 private void validateMethods( 397 TypeElement type, 398 ImmutableSet<ExecutableElement> abstractMethods, 399 ImmutableSet<ExecutableElement> toBuilderMethods, 400 ImmutableSet<ExecutableElement> propertyMethods, 401 boolean extensionsPresent) { 402 for (ExecutableElement method : abstractMethods) { 403 if (propertyMethods.contains(method)) { 404 checkReturnType(type, method); 405 } else if (!toBuilderMethods.contains(method) 406 && objectMethodToOverride(method) == ObjectMethod.NONE) { 407 // This could reasonably be an error, were it not for an Eclipse bug in 408 // ElementUtils.override that sometimes fails to recognize that one method overrides 409 // another, and therefore leaves us with both an abstract method and the subclass method 410 // that overrides it. This shows up in AutoValueTest.LukesBase for example. 411 String extensionMessage = extensionsPresent ? ", and no extension consumed it" : ""; 412 errorReporter() 413 .reportWarning( 414 method, 415 "[AutoValueBuilderWhat] Abstract method is neither a property getter nor a Builder" 416 + " converter%s", 417 extensionMessage); 418 } 419 } 420 errorReporter().abortIfAnyError(); 421 } 422 extensionName(AutoValueExtension extension)423 private String extensionName(AutoValueExtension extension) { 424 return extension.getClass().getName(); 425 } 426 defineVarsForType( TypeElement type, AutoValueTemplateVars vars, ImmutableSet<ExecutableElement> toBuilderMethods, ImmutableMap<ExecutableElement, TypeMirror> propertyMethodsAndTypes, Optional<BuilderSpec.Builder> maybeBuilder, Nullables nullables)427 private void defineVarsForType( 428 TypeElement type, 429 AutoValueTemplateVars vars, 430 ImmutableSet<ExecutableElement> toBuilderMethods, 431 ImmutableMap<ExecutableElement, TypeMirror> propertyMethodsAndTypes, 432 Optional<BuilderSpec.Builder> maybeBuilder, 433 Nullables nullables) { 434 ImmutableSet<ExecutableElement> propertyMethods = propertyMethodsAndTypes.keySet(); 435 vars.toBuilderMethods = 436 toBuilderMethods.stream().map(SimpleMethod::new).collect(toImmutableList()); 437 vars.toBuilderConstructor = !vars.toBuilderMethods.isEmpty(); 438 ImmutableListMultimap<ExecutableElement, AnnotationMirror> annotatedPropertyFields = 439 propertyFieldAnnotationMap(type, propertyMethods); 440 ImmutableListMultimap<ExecutableElement, AnnotationMirror> annotatedPropertyMethods = 441 propertyMethodAnnotationMap(type, propertyMethods, typeUtils()); 442 vars.props = 443 propertySet( 444 propertyMethodsAndTypes, 445 annotatedPropertyFields, 446 annotatedPropertyMethods, 447 nullables); 448 // Check for @AutoValue.Builder and add appropriate variables if it is present. 449 maybeBuilder.ifPresent( 450 builder -> { 451 ImmutableBiMap<ExecutableElement, String> methodToPropertyName = 452 propertyNameToMethodMap(propertyMethods).inverse(); 453 builder.defineVarsForAutoValue(vars, methodToPropertyName, nullables); 454 vars.builderName = "Builder"; 455 vars.builderAnnotations = copiedClassAnnotations(builder.builderType()); 456 }); 457 } 458 459 @Override nullableAnnotationForMethod(ExecutableElement propertyMethod)460 Optional<String> nullableAnnotationForMethod(ExecutableElement propertyMethod) { 461 return nullableAnnotationFor(propertyMethod, propertyMethod.getReturnType()); 462 } 463 prefixedGettersIn(Iterable<ExecutableElement> methods)464 static ImmutableSet<ExecutableElement> prefixedGettersIn(Iterable<ExecutableElement> methods) { 465 ImmutableSet.Builder<ExecutableElement> getters = ImmutableSet.builder(); 466 for (ExecutableElement method : methods) { 467 String name = method.getSimpleName().toString(); 468 // TODO(emcmanus): decide whether getfoo() (without a capital) is a getter. Currently it is. 469 boolean get = name.startsWith("get") && !name.equals("get"); 470 boolean is = 471 name.startsWith("is") 472 && !name.equals("is") 473 && method.getReturnType().getKind() == TypeKind.BOOLEAN; 474 if (get || is) { 475 getters.add(method); 476 } 477 } 478 return getters.build(); 479 } 480 ancestorIsAutoValue(TypeElement type)481 private boolean ancestorIsAutoValue(TypeElement type) { 482 while (true) { 483 TypeMirror parentMirror = type.getSuperclass(); 484 if (parentMirror.getKind() == TypeKind.NONE) { 485 return false; 486 } 487 TypeElement parentElement = (TypeElement) typeUtils().asElement(parentMirror); 488 if (hasAnnotationMirror(parentElement, AUTO_VALUE_NAME)) { 489 return true; 490 } 491 type = parentElement; 492 } 493 } 494 implementsAnnotation(TypeElement type)495 private boolean implementsAnnotation(TypeElement type) { 496 return typeUtils().isAssignable(type.asType(), getTypeMirror(Annotation.class)); 497 } 498 getTypeMirror(Class<?> c)499 private TypeMirror getTypeMirror(Class<?> c) { 500 return processingEnv.getElementUtils().getTypeElement(c.getName()).asType(); 501 } 502 immutableSetDifference(ImmutableSet<E> a, ImmutableSet<E> b)503 private static <E> ImmutableSet<E> immutableSetDifference(ImmutableSet<E> a, ImmutableSet<E> b) { 504 if (Collections.disjoint(a, b)) { 505 return a; 506 } else { 507 return difference(a, b).immutableCopy(); 508 } 509 } 510 } 511