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.value.processor.ClassNames.AUTO_VALUE_NAME; 20 import static com.google.common.collect.Sets.difference; 21 import static com.google.common.collect.Sets.intersection; 22 import static java.util.stream.Collectors.joining; 23 import static java.util.stream.Collectors.toList; 24 25 import com.google.auto.service.AutoService; 26 import com.google.auto.value.extension.AutoValueExtension; 27 import com.google.common.annotations.VisibleForTesting; 28 import com.google.common.base.Strings; 29 import com.google.common.base.Throwables; 30 import com.google.common.collect.ImmutableBiMap; 31 import com.google.common.collect.ImmutableList; 32 import com.google.common.collect.ImmutableListMultimap; 33 import com.google.common.collect.ImmutableMap; 34 import com.google.common.collect.ImmutableSet; 35 import com.google.common.collect.Iterables; 36 import java.lang.annotation.Annotation; 37 import java.util.ArrayList; 38 import java.util.Collections; 39 import java.util.Comparator; 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.ElementKind; 50 import javax.lang.model.element.ExecutableElement; 51 import javax.lang.model.element.TypeElement; 52 import javax.lang.model.type.TypeKind; 53 import javax.lang.model.type.TypeMirror; 54 import net.ltgt.gradle.incap.IncrementalAnnotationProcessor; 55 import net.ltgt.gradle.incap.IncrementalAnnotationProcessorType; 56 57 /** 58 * Javac annotation processor (compiler plugin) for value types; user code never references this 59 * class. 60 * 61 * @see <a href="https://github.com/google/auto/tree/master/value">AutoValue User's Guide</a> 62 * @author Éamonn McManus 63 */ 64 @AutoService(Processor.class) 65 @SupportedAnnotationTypes(AUTO_VALUE_NAME) 66 @IncrementalAnnotationProcessor(IncrementalAnnotationProcessorType.DYNAMIC) 67 public class AutoValueProcessor extends AutoValueOrOneOfProcessor { 68 private static final String OMIT_IDENTIFIERS_OPTION = "com.google.auto.value.OmitIdentifiers"; 69 70 // We moved MemoizeExtension to a different package, which had an unexpected effect: 71 // now if an old version of AutoValue is in the class path, ServiceLoader can pick up both the 72 // old and the new versions of MemoizeExtension. So we exclude the old version if we see it. 73 // The new version will be bundled with this processor so we should always find it. 74 private static final String OLD_MEMOIZE_EXTENSION = 75 "com.google.auto.value.extension.memoized.MemoizeExtension"; 76 AutoValueProcessor()77 public AutoValueProcessor() { 78 this(AutoValueProcessor.class.getClassLoader()); 79 } 80 81 @VisibleForTesting AutoValueProcessor(ClassLoader loaderForExtensions)82 AutoValueProcessor(ClassLoader loaderForExtensions) { 83 super(AUTO_VALUE_NAME); 84 this.extensions = null; 85 this.loaderForExtensions = loaderForExtensions; 86 } 87 88 @VisibleForTesting AutoValueProcessor(Iterable<? extends AutoValueExtension> extensions)89 public AutoValueProcessor(Iterable<? extends AutoValueExtension> extensions) { 90 super(AUTO_VALUE_NAME); 91 this.extensions = ImmutableList.copyOf(extensions); 92 this.loaderForExtensions = null; 93 } 94 95 // Depending on how this AutoValueProcessor was constructed, we might already have a list of 96 // extensions when init() is run, or, if `extensions` is null, we have a ClassLoader that will be 97 // used to get the list using the ServiceLoader API. 98 private ImmutableList<AutoValueExtension> extensions; 99 private final ClassLoader loaderForExtensions; 100 101 @VisibleForTesting extensionsFromLoader(ClassLoader loader)102 static ImmutableList<AutoValueExtension> extensionsFromLoader(ClassLoader loader) { 103 return ImmutableList.copyOf( 104 Iterables.filter( 105 SimpleServiceLoader.load(AutoValueExtension.class, loader), 106 ext -> !ext.getClass().getName().equals(OLD_MEMOIZE_EXTENSION))); 107 } 108 109 @Override init(ProcessingEnvironment processingEnv)110 public synchronized void init(ProcessingEnvironment processingEnv) { 111 super.init(processingEnv); 112 113 if (extensions == null) { 114 try { 115 extensions = extensionsFromLoader(loaderForExtensions); 116 } catch (RuntimeException | Error e) { 117 String explain = 118 (e instanceof ServiceConfigurationError) 119 ? " This may be due to a corrupt jar file in the compiler's classpath." 120 : ""; 121 errorReporter() 122 .reportWarning( 123 null, 124 "[AutoValueExtensionsException] An exception occurred while looking for AutoValue" 125 + " extensions. No extensions will function.%s\n%s", 126 explain, 127 Throwables.getStackTraceAsString(e)); 128 extensions = ImmutableList.of(); 129 } 130 } 131 } 132 133 @Override getSupportedOptions()134 public Set<String> getSupportedOptions() { 135 ImmutableSet.Builder<String> builder = ImmutableSet.builder(); 136 AutoValueExtension.IncrementalExtensionType incrementalType = 137 extensions.stream() 138 .map(e -> e.incrementalType(processingEnv)) 139 .min(Comparator.naturalOrder()) 140 .orElse(AutoValueExtension.IncrementalExtensionType.ISOLATING); 141 builder.add(OMIT_IDENTIFIERS_OPTION).addAll(optionsFor(incrementalType)); 142 for (AutoValueExtension extension : extensions) { 143 builder.addAll(extension.getSupportedOptions()); 144 } 145 return builder.build(); 146 } 147 optionsFor( AutoValueExtension.IncrementalExtensionType incrementalType)148 private static ImmutableSet<String> optionsFor( 149 AutoValueExtension.IncrementalExtensionType incrementalType) { 150 switch (incrementalType) { 151 case ISOLATING: 152 return ImmutableSet.of(IncrementalAnnotationProcessorType.ISOLATING.getProcessorOption()); 153 case AGGREGATING: 154 return ImmutableSet.of(IncrementalAnnotationProcessorType.AGGREGATING.getProcessorOption()); 155 case UNKNOWN: 156 return ImmutableSet.of(); 157 } 158 throw new AssertionError(incrementalType); 159 } 160 generatedSubclassName(TypeElement type, int depth)161 static String generatedSubclassName(TypeElement type, int depth) { 162 return generatedClassName(type, Strings.repeat("$", depth) + "AutoValue_"); 163 } 164 165 @Override processType(TypeElement type)166 void processType(TypeElement type) { 167 if (!hasAnnotationMirror(type, AUTO_VALUE_NAME)) { 168 // This shouldn't happen unless the compilation environment is buggy, 169 // but it has happened in the past and can crash the compiler. 170 errorReporter() 171 .abortWithError( 172 type, 173 "[AutoValueCompilerBug] annotation processor for @AutoValue was invoked with a type" 174 + " that does not have that annotation; this is probably a compiler bug"); 175 } 176 if (type.getKind() != ElementKind.CLASS) { 177 errorReporter() 178 .abortWithError(type, "[AutoValueNotClass] @AutoValue only applies to classes"); 179 } 180 if (ancestorIsAutoValue(type)) { 181 errorReporter() 182 .abortWithError(type, "[AutoValueExtend] One @AutoValue class may not extend another"); 183 } 184 if (implementsAnnotation(type)) { 185 errorReporter() 186 .abortWithError( 187 type, 188 "[AutoValueImplAnnotation] @AutoValue may not be used to implement an annotation" 189 + " interface; try using @AutoAnnotation instead"); 190 } 191 checkModifiersIfNested(type); 192 193 // We are going to classify the methods of the @AutoValue class into several categories. 194 // This covers the methods in the class itself and the ones it inherits from supertypes. 195 // First, the only concrete (non-abstract) methods we are interested in are overrides of 196 // Object methods (equals, hashCode, toString), which signal that we should not generate 197 // an implementation of those methods. 198 // Then, each abstract method is one of the following: 199 // (1) A property getter, like "abstract String foo()" or "abstract String getFoo()". 200 // (2) A toBuilder() method, which is any abstract no-arg method returning the Builder for 201 // this @AutoValue class. 202 // (3) An abstract method that will be consumed by an extension, such as 203 // Parcelable.describeContents() or Parcelable.writeToParcel(Parcel, int). 204 // The describeContents() example shows a quirk here: initially we will identify it as a 205 // property, which means that we need to reconstruct the list of properties after allowing 206 // extensions to consume abstract methods. 207 // If there are abstract methods that don't fit any of the categories above, that is an error 208 // which we signal explicitly to avoid confusion. 209 210 ImmutableSet<ExecutableElement> methods = 211 getLocalAndInheritedMethods( 212 type, processingEnv.getTypeUtils(), processingEnv.getElementUtils()); 213 ImmutableSet<ExecutableElement> abstractMethods = abstractMethodsIn(methods); 214 215 BuilderSpec builderSpec = new BuilderSpec(type, processingEnv, errorReporter()); 216 Optional<BuilderSpec.Builder> builder = builderSpec.getBuilder(); 217 ImmutableSet<ExecutableElement> toBuilderMethods; 218 if (builder.isPresent()) { 219 toBuilderMethods = builder.get().toBuilderMethods(typeUtils(), abstractMethods); 220 } else { 221 toBuilderMethods = ImmutableSet.of(); 222 } 223 224 ImmutableMap<ExecutableElement, TypeMirror> propertyMethodsAndTypes = 225 propertyMethodsIn(immutableSetDifference(abstractMethods, toBuilderMethods), type); 226 ImmutableMap<String, ExecutableElement> properties = 227 propertyNameToMethodMap(propertyMethodsAndTypes.keySet()); 228 229 ExtensionContext context = new ExtensionContext( 230 processingEnv, type, properties, propertyMethodsAndTypes, abstractMethods); 231 ImmutableList<AutoValueExtension> applicableExtensions = applicableExtensions(type, context); 232 ImmutableSet<ExecutableElement> consumedMethods = 233 methodsConsumedByExtensions( 234 type, applicableExtensions, context, abstractMethods, properties); 235 236 if (!consumedMethods.isEmpty()) { 237 ImmutableSet<ExecutableElement> allAbstractMethods = abstractMethods; 238 abstractMethods = immutableSetDifference(abstractMethods, consumedMethods); 239 toBuilderMethods = immutableSetDifference(toBuilderMethods, consumedMethods); 240 propertyMethodsAndTypes = 241 propertyMethodsIn(immutableSetDifference(abstractMethods, toBuilderMethods), type); 242 properties = propertyNameToMethodMap(propertyMethodsAndTypes.keySet()); 243 context = new ExtensionContext( 244 processingEnv, type, properties, propertyMethodsAndTypes, allAbstractMethods); 245 } 246 247 ImmutableSet<ExecutableElement> propertyMethods = propertyMethodsAndTypes.keySet(); 248 boolean extensionsPresent = !applicableExtensions.isEmpty(); 249 validateMethods(type, abstractMethods, toBuilderMethods, propertyMethods, extensionsPresent); 250 251 String finalSubclass = generatedSubclassName(type, 0); 252 AutoValueTemplateVars vars = new AutoValueTemplateVars(); 253 vars.finalSubclass = TypeSimplifier.simpleNameOf(finalSubclass); 254 vars.types = processingEnv.getTypeUtils(); 255 vars.identifiers = !processingEnv.getOptions().containsKey(OMIT_IDENTIFIERS_OPTION); 256 defineSharedVarsForType(type, methods, vars); 257 defineVarsForType(type, vars, toBuilderMethods, propertyMethodsAndTypes, builder); 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); 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)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 ImmutableSet<ExecutableElement> propertyMethods = propertyMethodsAndTypes.keySet(); 434 // We can't use ImmutableList.toImmutableList() for obscure Google-internal reasons. 435 vars.toBuilderMethods = 436 ImmutableList.copyOf(toBuilderMethods.stream().map(SimpleMethod::new).collect(toList())); 437 ImmutableListMultimap<ExecutableElement, AnnotationMirror> annotatedPropertyFields = 438 propertyFieldAnnotationMap(type, propertyMethods); 439 ImmutableListMultimap<ExecutableElement, AnnotationMirror> annotatedPropertyMethods = 440 propertyMethodAnnotationMap(type, propertyMethods); 441 vars.props = 442 propertySet(propertyMethodsAndTypes, annotatedPropertyFields, annotatedPropertyMethods); 443 vars.serialVersionUID = getSerialVersionUID(type); 444 // Check for @AutoValue.Builder and add appropriate variables if it is present. 445 maybeBuilder.ifPresent( 446 builder -> { 447 ImmutableBiMap<ExecutableElement, String> methodToPropertyName = 448 propertyNameToMethodMap(propertyMethods).inverse(); 449 builder.defineVars(vars, methodToPropertyName); 450 vars.builderAnnotations = copiedClassAnnotations(builder.builderType()); 451 }); 452 } 453 454 @Override nullableAnnotationForMethod(ExecutableElement propertyMethod)455 Optional<String> nullableAnnotationForMethod(ExecutableElement propertyMethod) { 456 return nullableAnnotationFor(propertyMethod, propertyMethod.getReturnType()); 457 } 458 prefixedGettersIn(Iterable<ExecutableElement> methods)459 static ImmutableSet<ExecutableElement> prefixedGettersIn(Iterable<ExecutableElement> methods) { 460 ImmutableSet.Builder<ExecutableElement> getters = ImmutableSet.builder(); 461 for (ExecutableElement method : methods) { 462 String name = method.getSimpleName().toString(); 463 // TODO(emcmanus): decide whether getfoo() (without a capital) is a getter. Currently it is. 464 boolean get = name.startsWith("get") && !name.equals("get"); 465 boolean is = 466 name.startsWith("is") 467 && !name.equals("is") 468 && method.getReturnType().getKind() == TypeKind.BOOLEAN; 469 if (get || is) { 470 getters.add(method); 471 } 472 } 473 return getters.build(); 474 } 475 ancestorIsAutoValue(TypeElement type)476 private boolean ancestorIsAutoValue(TypeElement type) { 477 while (true) { 478 TypeMirror parentMirror = type.getSuperclass(); 479 if (parentMirror.getKind() == TypeKind.NONE) { 480 return false; 481 } 482 TypeElement parentElement = (TypeElement) typeUtils().asElement(parentMirror); 483 if (hasAnnotationMirror(parentElement, AUTO_VALUE_NAME)) { 484 return true; 485 } 486 type = parentElement; 487 } 488 } 489 implementsAnnotation(TypeElement type)490 private boolean implementsAnnotation(TypeElement type) { 491 return typeUtils().isAssignable(type.asType(), getTypeMirror(Annotation.class)); 492 } 493 getTypeMirror(Class<?> c)494 private TypeMirror getTypeMirror(Class<?> c) { 495 return processingEnv.getElementUtils().getTypeElement(c.getName()).asType(); 496 } 497 immutableSetDifference(ImmutableSet<E> a, ImmutableSet<E> b)498 private static <E> ImmutableSet<E> immutableSetDifference(ImmutableSet<E> a, ImmutableSet<E> b) { 499 if (Collections.disjoint(a, b)) { 500 return a; 501 } else { 502 return ImmutableSet.copyOf(difference(a, b)); 503 } 504 } 505 } 506