• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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