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