• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2021 The Dagger Authors.
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 
17 package dagger.internal.codegen.base;
18 
19 import static androidx.room.compiler.processing.XElementKt.isMethod;
20 import static androidx.room.compiler.processing.XElementKt.isTypeElement;
21 import static androidx.room.compiler.processing.XElementKt.isVariableElement;
22 import static androidx.room.compiler.processing.XTypeKt.isArray;
23 import static androidx.room.compiler.processing.compat.XConverters.toJavac;
24 import static androidx.room.compiler.processing.compat.XConverters.toXProcessing;
25 import static com.google.common.base.Preconditions.checkNotNull;
26 import static dagger.internal.codegen.extension.DaggerStreams.toImmutableList;
27 import static dagger.internal.codegen.xprocessing.XAnnotationValues.getKindName;
28 import static dagger.internal.codegen.xprocessing.XElements.asEnumEntry;
29 import static dagger.internal.codegen.xprocessing.XElements.asExecutable;
30 import static dagger.internal.codegen.xprocessing.XElements.asMethod;
31 import static dagger.internal.codegen.xprocessing.XElements.asTypeElement;
32 import static dagger.internal.codegen.xprocessing.XElements.asTypeParameter;
33 import static dagger.internal.codegen.xprocessing.XElements.asVariable;
34 import static dagger.internal.codegen.xprocessing.XElements.getKindName;
35 import static dagger.internal.codegen.xprocessing.XElements.isEnumEntry;
36 import static dagger.internal.codegen.xprocessing.XElements.isExecutable;
37 import static dagger.internal.codegen.xprocessing.XElements.isTypeParameter;
38 import static dagger.internal.codegen.xprocessing.XExecutableTypes.asMethodType;
39 import static dagger.internal.codegen.xprocessing.XExecutableTypes.getKindName;
40 import static dagger.internal.codegen.xprocessing.XExecutableTypes.isMethodType;
41 import static dagger.internal.codegen.xprocessing.XTypes.asArray;
42 import static dagger.internal.codegen.xprocessing.XTypes.getKindName;
43 import static dagger.internal.codegen.xprocessing.XTypes.isDeclared;
44 import static dagger.internal.codegen.xprocessing.XTypes.isTypeOf;
45 import static dagger.internal.codegen.xprocessing.XTypes.isWildcard;
46 
47 import androidx.room.compiler.processing.XAnnotation;
48 import androidx.room.compiler.processing.XAnnotationValue;
49 import androidx.room.compiler.processing.XElement;
50 import androidx.room.compiler.processing.XExecutableElement;
51 import androidx.room.compiler.processing.XExecutableType;
52 import androidx.room.compiler.processing.XProcessingEnv;
53 import androidx.room.compiler.processing.XProcessingEnv.Backend;
54 import androidx.room.compiler.processing.XType;
55 import androidx.room.compiler.processing.XTypeElement;
56 import androidx.room.compiler.processing.compat.XConverters;
57 import com.google.common.base.Ascii;
58 import com.google.common.collect.ImmutableList;
59 import com.squareup.javapoet.ClassName;
60 import dagger.Reusable;
61 import dagger.internal.codegen.compileroption.CompilerOptions;
62 import dagger.internal.codegen.javapoet.TypeNames;
63 import dagger.internal.codegen.xprocessing.XAnnotationValues;
64 import dagger.internal.codegen.xprocessing.XAnnotations;
65 import dagger.internal.codegen.xprocessing.XElements;
66 import dagger.internal.codegen.xprocessing.XExecutableTypes;
67 import dagger.internal.codegen.xprocessing.XTypes;
68 import java.util.ArrayList;
69 import java.util.Collection;
70 import java.util.List;
71 import java.util.Optional;
72 import javax.inject.Inject;
73 
74 /**
75  * A fork of {@link com.google.auto.common.SuperficialValidation}.
76  *
77  * <p>This fork makes a couple changes from the original:
78  *
79  * <ul>
80  *   <li>Throws {@link ValidationException} rather than returning {@code false} for invalid types.
81  *   <li>Fixes a bug that incorrectly validates error types in annotations (b/213880825)
82  *   <li>Exposes extra methods needed to validate various parts of an element rather than just the
83  *       entire element.
84  * </ul>
85  */
86 @Reusable
87 public final class DaggerSuperficialValidation {
88   /**
89    * Returns the type element with the given class name or throws {@link ValidationException} if it
90    * is not accessible in the current compilation.
91    */
requireTypeElement(XProcessingEnv processingEnv, ClassName className)92   public static XTypeElement requireTypeElement(XProcessingEnv processingEnv, ClassName className) {
93     return requireTypeElement(processingEnv, className.canonicalName());
94   }
95 
96   /**
97    * Returns the type element with the given class name or throws {@link ValidationException} if it
98    * is not accessible in the current compilation.
99    */
requireTypeElement(XProcessingEnv processingEnv, String className)100   public static XTypeElement requireTypeElement(XProcessingEnv processingEnv, String className) {
101     XTypeElement type = processingEnv.findTypeElement(className);
102     if (type == null) {
103       throw new ValidationException.KnownErrorType(className);
104     }
105     return type;
106   }
107 
108   private final boolean isStrictValidationEnabled;
109   private final XProcessingEnv processingEnv;
110 
111   @Inject
DaggerSuperficialValidation(XProcessingEnv processingEnv, CompilerOptions compilerOptions)112   DaggerSuperficialValidation(XProcessingEnv processingEnv, CompilerOptions compilerOptions) {
113     this.processingEnv = processingEnv;
114     this.isStrictValidationEnabled = compilerOptions.strictSuperficialValidation();
115   }
116 
117   /**
118    * Validates the {@link XElement#getType()} type of the given element.
119    *
120    * <p>Validating the type also validates any types it references, such as any type arguments or
121    * type bounds. For an {@link XExecutableType}, the parameter and return types must be fully
122    * defined, as must types declared in a {@code throws} clause or in the bounds of any type
123    * parameters.
124    */
validateTypeOf(XElement element)125   public void validateTypeOf(XElement element) {
126     try {
127       // In XProcessing, there is no generic way to get an element "asType" so we break this down
128       // differently for different element kinds.
129       if (isTypeElement(element)) {
130         validateType(Ascii.toLowerCase(getKindName(element)), asTypeElement(element).getType());
131       } else if (isVariableElement(element)) {
132         validateType(
133             Ascii.toLowerCase(getKindName(element)) + " type", asVariable(element).getType());
134       } else if (isExecutable(element)) {
135         validateExecutableType(asExecutable(element).getExecutableType());
136       } else if (isEnumEntry(element)) {
137         validateType(
138             Ascii.toLowerCase(getKindName(element)),
139             asEnumEntry(element).getEnumTypeElement().getType());
140       }
141     } catch (RuntimeException exception) {
142       throw ValidationException.from(exception).append(element);
143     }
144   }
145 
146   /**
147    * Validates the {@link XElement#getSuperType()} type of the given element.
148    *
149    * <p>Validating the type also validates any types it references, such as any type arguments or
150    * type bounds.
151    */
validateSuperTypeOf(XTypeElement element)152   public void validateSuperTypeOf(XTypeElement element) {
153     try {
154       validateType("superclass", element.getSuperType());
155     } catch (RuntimeException exception) {
156       throw ValidationException.from(exception).append(element);
157     }
158   }
159 
160   /**
161    * Validates the {@link XExecutableElement#getThrownTypes()} types of the given element.
162    *
163    * <p>Validating the type also validates any types it references, such as any type arguments or
164    * type bounds.
165    */
validateThrownTypesOf(XExecutableElement element)166   public void validateThrownTypesOf(XExecutableElement element) {
167     try {
168       validateTypes("thrown type", element.getThrownTypes());
169     } catch (RuntimeException exception) {
170       throw ValidationException.from(exception).append(element);
171     }
172   }
173 
174   /**
175    * Validates the annotation types of the given element.
176    *
177    * <p>Note: this method does not validate annotation values. This method is useful if you care
178    * about the annotation's annotations (e.g. to check for {@code Scope} or {@code Qualifier}). In
179    * such cases, we just need to validate the annotation's type.
180    */
validateAnnotationTypesOf(XElement element)181   public void validateAnnotationTypesOf(XElement element) {
182     element
183         .getAllAnnotations()
184         .forEach(annotation -> validateAnnotationTypeOf(element, annotation));
185   }
186 
187   /**
188    * Validates the type of the given annotation.
189    *
190    * <p>The annotation is assumed to be annotating the given element, but this is not checked. The
191    * element is only in the error message if a {@link ValidatationException} is thrown.
192    *
193    * <p>Note: this method does not validate annotation values. This method is useful if you care
194    * about the annotation's annotations (e.g. to check for {@code Scope} or {@code Qualifier}). In
195    * such cases, we just need to validate the annotation's type.
196    */
197   // TODO(bcorso): See CL/427767370 for suggestions to make this API clearer.
validateAnnotationTypeOf(XElement element, XAnnotation annotation)198   public void validateAnnotationTypeOf(XElement element, XAnnotation annotation) {
199     try {
200       validateType("annotation type", annotation.getType());
201     } catch (RuntimeException exception) {
202       throw ValidationException.from(exception).append(annotation).append(element);
203     }
204   }
205 
206   /** Validate the annotations of the given element. */
validateAnnotationsOf(XElement element)207   public void validateAnnotationsOf(XElement element) {
208     try {
209       validateAnnotations(element.getAllAnnotations());
210     } catch (RuntimeException exception) {
211       throw ValidationException.from(exception).append(element);
212     }
213   }
214 
validateAnnotationOf(XElement element, XAnnotation annotation)215   public void validateAnnotationOf(XElement element, XAnnotation annotation) {
216     try {
217       validateAnnotation(annotation);
218     } catch (RuntimeException exception) {
219       throw ValidationException.from(exception).append(element);
220     }
221   }
222 
223   /**
224    * Validate the type hierarchy for the given type (with the given type description) within the
225    * given element.
226    *
227    * <p>Validation includes all superclasses, interfaces, and type parameters of those types.
228    */
validateTypeHierarchyOf(String typeDescription, XElement element, XType type)229   public void validateTypeHierarchyOf(String typeDescription, XElement element, XType type) {
230     try {
231       validateTypeHierarchy(typeDescription, type);
232     } catch (RuntimeException exception) {
233       throw ValidationException.from(exception).append(element);
234     }
235   }
236 
validateTypeHierarchy(String desc, XType type)237   private void validateTypeHierarchy(String desc, XType type) {
238     validateType(desc, type);
239     try {
240       type.getSuperTypes().forEach(supertype -> validateTypeHierarchy("supertype", supertype));
241     } catch (RuntimeException exception) {
242       throw ValidationException.from(exception).append(desc, type);
243     }
244   }
245 
246   /**
247    * Returns true if all of the given elements return true from {@link #validateElement(XElement)}.
248    */
validateElements(Collection<? extends XElement> elements)249   private void validateElements(Collection<? extends XElement> elements) {
250     elements.forEach(this::validateElement);
251   }
252 
253   /**
254    * Returns true if all types referenced by the given element are defined. The exact meaning of
255    * this depends on the kind of element. For packages, it means that all annotations on the package
256    * are fully defined. For other element kinds, it means that types referenced by the element,
257    * anything it contains, and any of its annotations element are all defined.
258    */
validateElement(XElement element)259   public void validateElement(XElement element) {
260     checkNotNull(element);
261 
262     // Validate the annotations first since these are common to all element kinds. We don't
263     // need to wrap these in try-catch because the *Of() methods are already wrapped.
264     validateAnnotationsOf(element);
265 
266     // Validate enclosed elements based on the given element's kind.
267     try {
268       if (isTypeElement(element)) {
269         XTypeElement typeElement = asTypeElement(element);
270         validateElements(typeElement.getTypeParameters());
271         validateTypes("interface", typeElement.getSuperInterfaces());
272         if (typeElement.getSuperType() != null) {
273           validateType("superclass", typeElement.getSuperType());
274         }
275         // TODO (b/286313067) move the logic to ComponentValidator once the validation logic is
276         // split into individual validators to satisfy different needs.
277         // Dagger doesn't use components' static method, therefore, they shouldn't be validated to
278         // be able to stop component generation.
279         if (typeElement.hasAnnotation(TypeNames.COMPONENT)) {
280           validateElements(
281               typeElement.getEnclosedElements().stream()
282                   .filter(member -> !XElements.isStatic(member))
283                   .collect(toImmutableList()));
284         } else {
285           validateElements(typeElement.getEnclosedElements());
286         }
287       } else if (isExecutable(element)) {
288         if (isMethod(element)) {
289           validateType("return type", asMethod(element).getReturnType());
290         }
291         XExecutableElement executableElement = asExecutable(element);
292         validateTypes("thrown type", executableElement.getThrownTypes());
293         validateElements(executableElement.getTypeParameters());
294         validateElements(executableElement.getParameters());
295       } else if (isTypeParameter(element)) {
296         validateTypes("bound type", asTypeParameter(element).getBounds());
297       }
298     } catch (RuntimeException exception) {
299       throw ValidationException.from(exception).append(element);
300     }
301 
302     // Validate the type last. This allows errors on more specific elements to be caught above.
303     // E.g. errors on parameters will be attributed to the parameter elements rather than the method
304     // type, which generally leads to nicer error messages. We don't need to wrap these in try-catch
305     // because the *Of() methods are already wrapped.
306     validateTypeOf(element);
307   }
308 
validateTypes(String desc, Collection<? extends XType> types)309   private void validateTypes(String desc, Collection<? extends XType> types) {
310     types.forEach(type -> validateType(desc, type));
311   }
312 
313   /**
314    * Returns true if the given type is fully defined. This means that the type itself is defined, as
315    * are any types it references, such as any type arguments or type bounds.
316    */
validateType(String desc, XType type)317   private void validateType(String desc, XType type) {
318     checkNotNull(type);
319     // TODO(b/242569252): Due to a bug in kotlinc, a TypeName may incorrectly contain a "$" instead
320     // of "." if the TypeName is requested before the type has been resolved. Furthermore,
321     // XProcessing will cache the incorrect TypeName so that further calls will still contain the
322     // "$" even after the type has been resolved. Thus, we try to resolve the type as early as
323     // possible to prevent using/caching the incorrect TypeName.
324     XTypes.resolveIfNeeded(type);
325     try {
326       if (isArray(type)) {
327         validateType("array component type", asArray(type).getComponentType());
328       } else if (isDeclared(type)) {
329         if (isStrictValidationEnabled) {
330           // There's a bug in TypeVisitor which will visit the visitDeclared() method rather than
331           // visitError() even when it's an ERROR kind. Thus, we check the kind directly here and
332           // fail validation if it's an ERROR kind (see b/213880825).
333           if (isErrorKind(type)) {
334             throw new ValidationException.KnownErrorType(type);
335           }
336         }
337         type.getTypeArguments().forEach(typeArg -> validateType("type argument", typeArg));
338       } else if (isWildcard(type)) {
339         if (type.extendsBound() != null) {
340           validateType("extends bound type", type.extendsBound());
341         }
342       } else if (isErrorKind(type)) {
343         throw new ValidationException.KnownErrorType(type);
344       }
345     } catch (RuntimeException e) {
346       throw ValidationException.from(e).append(desc, type);
347     }
348   }
349 
350   // TODO(bcorso): Consider moving this over to XProcessing. There's some complication due to
351   // b/248552462 and the fact that XProcessing also uses the error.NonExistentClass type for invalid
352   // types in KSP, which we may want to keep as error kinds in KSP.
isErrorKind(XType type)353   private boolean isErrorKind(XType type) {
354     // https://youtrack.jetbrains.com/issue/KT-34193/Kapt-CorrectErrorTypes-doesnt-work-for-generics
355     // XProcessing treats 'error.NonExistentClass' as an error type. However, due to the bug in KAPT
356     // (linked above), 'error.NonExistentClass' can still be referenced in the stub classes even
357     // when 'correctErrorTypes=true' is enabled. Thus, we can't treat 'error.NonExistentClass' as an
358     // actual error type, as that would completely prevent processing of stubs that exhibit this
359     // bug. This behavior also matches how things work in Javac, as 'error.NonExistentClass' is
360     // treated as a TypeKind.DECLARED rather than a TypeKind.ERROR since the type is a real class
361     // that exists on the classpath.
362     return type.isError()
363         && !(processingEnv.getBackend() == Backend.JAVAC
364             && type.getTypeName().toString().contentEquals("error.NonExistentClass"));
365   }
366 
367   /**
368    * Returns true if the given type is fully defined. This means that the parameter and return types
369    * must be fully defined, as must types declared in a {@code throws} clause or in the bounds of
370    * any type parameters.
371    */
validateExecutableType(XExecutableType type)372   private void validateExecutableType(XExecutableType type) {
373     try {
374       validateTypes("parameter type", type.getParameterTypes());
375       validateTypes("thrown type", type.getThrownTypes());
376       validateTypes("type variable", getTypeVariables(type));
377       if (isMethodType(type)) {
378         validateType("return type", asMethodType(type).getReturnType());
379       }
380     } catch (RuntimeException e) {
381       throw ValidationException.from(e).append(type);
382     }
383   }
384 
getTypeVariables(XExecutableType executableType)385   private ImmutableList<XType> getTypeVariables(XExecutableType executableType) {
386     switch (processingEnv.getBackend()) {
387       case JAVAC:
388         return toJavac(executableType).getTypeVariables().stream()
389             .map(typeVariable -> toXProcessing(typeVariable, processingEnv))
390             .collect(toImmutableList());
391       case KSP:
392         // TODO(b/247851395): Add a way to get type variables as XTypes from XExecutableType --
393         // currently, we can only get TypeVariableNames from XMethodType. For now, just skip
394         // validating type variables of methods in KSP.
395         return ImmutableList.of();
396     }
397     throw new AssertionError("Unexpected backend: " + processingEnv.getBackend());
398   }
399 
validateAnnotations(Collection<XAnnotation> annotations)400   private void validateAnnotations(Collection<XAnnotation> annotations) {
401     annotations.forEach(this::validateAnnotation);
402   }
403 
validateAnnotation(XAnnotation annotation)404   private void validateAnnotation(XAnnotation annotation) {
405     try {
406       validateType("annotation type", annotation.getType());
407       try {
408         // Note: We separate this into its own try-catch since there's a bug where we could get an
409         // error when getting the annotation values due to b/264089557. This way we will at least
410         // report the name of the annotation in the error message.
411         validateAnnotationValues(getDefaultValues(annotation));
412         validateAnnotationValues(annotation.getAnnotationValues());
413       } catch (RuntimeException exception) {
414         throw ValidationException.from(exception).append(annotation);
415       }
416     } catch (RuntimeException exception) {
417       throw ValidationException.from(exception)
418           .append(
419               "annotation type: "
420                   + (annotation.getType().isError()
421                       ? annotation.getName() // SUPPRESS_GET_NAME_CHECK
422                       : annotation.getClassName().canonicalName()));
423     }
424   }
425 
getDefaultValues(XAnnotation annotation)426   private ImmutableList<XAnnotationValue> getDefaultValues(XAnnotation annotation) {
427     switch (processingEnv.getBackend()) {
428       case JAVAC:
429         return annotation.getTypeElement().getDeclaredMethods().stream()
430             .map(XConverters::toJavac)
431             .filter(method -> method.getDefaultValue() != null)
432             .map(method -> toXProcessing(method.getDefaultValue(), method, processingEnv))
433             .collect(toImmutableList());
434       case KSP:
435         // TODO(b/231170716): Add a generic way to retrieve default values from XAnnotation
436         // For now, just ignore them in KSP when doing validation.
437         return ImmutableList.of();
438     }
439     throw new AssertionError("Unexpected backend: " + processingEnv.getBackend());
440   }
441 
validateAnnotationValues(Collection<XAnnotationValue> values)442   private void validateAnnotationValues(Collection<XAnnotationValue> values) {
443     values.forEach(this::validateAnnotationValue);
444   }
445 
validateAnnotationValue(XAnnotationValue value)446   private void validateAnnotationValue(XAnnotationValue value) {
447     try {
448       XType expectedType = value.getValueType();
449 
450       // TODO(b/249834057): In KSP error types in annotation values are just null, so check this
451       // first and throw KnownErrorType of "<error>" to match Javac for now.
452       if (processingEnv.getBackend() == Backend.KSP && value.getValue() == null) {
453         throw new ValidationException.KnownErrorType("<error>");
454       }
455 
456       if (value.hasListValue()) {
457         validateAnnotationValues(value.asAnnotationValueList());
458       } else if (value.hasAnnotationValue()) {
459         validateIsEquivalentType(value.asAnnotation().getType(), expectedType);
460         validateAnnotation(value.asAnnotation());
461       } else if (value.hasEnumValue()) {
462         validateIsEquivalentType(value.asEnum().getEnumTypeElement().getType(), expectedType);
463         validateElement(value.asEnum());
464       } else if (value.hasTypeValue()) {
465         validateType("annotation value type", value.asType());
466       } else {
467         // Validates all other types, e.g. primitives and String values.
468         validateIsTypeOf(expectedType, ClassName.get(value.getValue().getClass()));
469       }
470     } catch (RuntimeException e) {
471       throw ValidationException.from(e).append(value);
472     }
473   }
474 
validateIsTypeOf(XType expectedType, ClassName className)475   private void validateIsTypeOf(XType expectedType, ClassName className) {
476     if (!isTypeOf(expectedType.boxed(), className)) {
477       throw new ValidationException.UnknownErrorType();
478     }
479   }
480 
validateIsEquivalentType(XType type, XType expectedType)481   private void validateIsEquivalentType(XType type, XType expectedType) {
482     if (!XTypes.equivalence().equivalent(type, expectedType)) {
483       throw new ValidationException.KnownErrorType(type);
484     }
485   }
486 
487   /**
488    * A runtime exception that can be used during superficial validation to collect information about
489    * unexpected exceptions during validation.
490    */
491   public abstract static class ValidationException extends RuntimeException {
492     /** A {@link ValidationException} that originated from an unexpected exception. */
493     public static final class UnexpectedException extends ValidationException {
UnexpectedException(Throwable throwable)494       private UnexpectedException(Throwable throwable) {
495         super(throwable);
496       }
497     }
498 
499     /** A {@link ValidationException} that originated from a known error type. */
500     public static final class KnownErrorType extends ValidationException {
501       private final String errorTypeName;
502 
KnownErrorType(XType errorType)503       private KnownErrorType(XType errorType) {
504         this.errorTypeName = XTypes.toStableString(errorType);
505       }
506 
KnownErrorType(String errorTypeName)507       private KnownErrorType(String errorTypeName) {
508         this.errorTypeName = errorTypeName;
509       }
510 
getErrorTypeName()511       public String getErrorTypeName() {
512         return errorTypeName;
513       }
514     }
515 
516     /** A {@link ValidationException} that originated from an unknown error type. */
517     public static final class UnknownErrorType extends ValidationException {}
518 
from(Throwable throwable)519     private static ValidationException from(Throwable throwable) {
520       if (throwable instanceof ValidationException) {
521         // We only ever create one instance of the ValidationException.
522         return (ValidationException) throwable;
523       } else if (throwable instanceof TypeNotPresentException) {
524         // XProcessing can throw TypeNotPresentException, so grab the error type from there if so.
525         return new KnownErrorType(((TypeNotPresentException) throwable).typeName());
526       }
527       return new UnexpectedException(throwable);
528     }
529 
530     private Optional<XElement> lastReportedElement = Optional.empty();
531     private final List<String> messages = new ArrayList<>();
532 
ValidationException()533     private ValidationException() {
534       super("");
535     }
536 
ValidationException(Throwable throwable)537     private ValidationException(Throwable throwable) {
538       super("", throwable);
539     }
540 
541     /**
542      * Appends a message for the given element and returns this instance of {@link
543      * ValidationException}
544      */
append(XElement element)545     private ValidationException append(XElement element) {
546       lastReportedElement = Optional.of(element);
547       return append(getMessageForElement(element));
548     }
549 
550     /**
551      * Appends a message for the given type and returns this instance of {@link ValidationException}
552      */
append(String desc, XType type)553     private ValidationException append(String desc, XType type) {
554       return append(
555           String.format(
556               "type (%s %s): %s",
557               getKindName(type),
558               desc,
559               XTypes.toStableString(type)));
560     }
561 
562     /**
563      * Appends a message for the given executable type and returns this instance of {@link
564      * ValidationException}
565      */
append(XExecutableType type)566     private ValidationException append(XExecutableType type) {
567       return append(
568           String.format(
569               "type (EXECUTABLE %s): %s",
570               Ascii.toLowerCase(getKindName(type)),
571               XExecutableTypes.toStableString(type)));
572     }
573     /**
574      * Appends a message for the given annotation and returns this instance of {@link
575      * ValidationException}
576      */
append(XAnnotation annotation)577     private ValidationException append(XAnnotation annotation) {
578       // Note: Calling #toString() directly on the annotation throws NPE (b/216180336).
579       return append(String.format("annotation: %s", XAnnotations.toStableString(annotation)));
580     }
581 
582     /** Appends the given message and returns this instance of {@link ValidationException} */
append(String message)583     private ValidationException append(String message) {
584       messages.add(message);
585       return this;
586     }
587 
588     /**
589      * Appends a message for the given annotation value and returns this instance of {@link
590      * ValidationException}
591      */
append(XAnnotationValue value)592     private ValidationException append(XAnnotationValue value) {
593       return append(
594           String.format(
595               "annotation value (%s): %s=%s",
596               getKindName(value),
597               value.getName(),  // SUPPRESS_GET_NAME_CHECK
598               XAnnotationValues.toStableString(value)));
599     }
600 
601     @Override
getMessage()602     public String getMessage() {
603       return String.format("\n  Validation trace:\n    => %s", getTrace());
604     }
605 
getTrace()606     public String getTrace() {
607       return String.join("\n    => ", getMessageInternal().reverse());
608     }
609 
getMessageInternal()610     private ImmutableList<String> getMessageInternal() {
611       if (!lastReportedElement.isPresent()) {
612         return ImmutableList.copyOf(messages);
613       }
614       // Append any enclosing element information if needed.
615       List<String> newMessages = new ArrayList<>(messages);
616       XElement element = lastReportedElement.get();
617       while (shouldAppendEnclosingElement(element)) {
618         element = element.getEnclosingElement();
619         newMessages.add(getMessageForElement(element));
620       }
621       return ImmutableList.copyOf(newMessages);
622     }
623 
shouldAppendEnclosingElement(XElement element)624     private static boolean shouldAppendEnclosingElement(XElement element) {
625       return element.getEnclosingElement() != null
626           // We don't report enclosing elements for types because the type name should contain any
627           // enclosing type and package information we need.
628           && !isTypeElement(element)
629           && (isExecutable(element.getEnclosingElement())
630               || isTypeElement(element.getEnclosingElement()));
631     }
632 
getMessageForElement(XElement element)633     private String getMessageForElement(XElement element) {
634       return String.format(
635           "element (%s): %s",
636           Ascii.toUpperCase(getKindName(element)),
637           XElements.toStableString(element));
638     }
639   }
640 }
641