001/* 002 * Copyright (C) 2005 The Guava Authors 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016 017package com.google.common.testing; 018 019import static com.google.common.base.Preconditions.checkArgument; 020import static com.google.common.base.Preconditions.checkNotNull; 021import static java.util.Arrays.stream; 022import static java.util.Objects.requireNonNull; 023import static java.util.stream.Stream.concat; 024 025import com.google.common.annotations.GwtIncompatible; 026import com.google.common.annotations.J2ktIncompatible; 027import com.google.common.base.Converter; 028import com.google.common.base.Objects; 029import com.google.common.collect.ClassToInstanceMap; 030import com.google.common.collect.ImmutableList; 031import com.google.common.collect.ImmutableSet; 032import com.google.common.collect.Lists; 033import com.google.common.collect.Maps; 034import com.google.common.collect.MutableClassToInstanceMap; 035import com.google.common.reflect.Invokable; 036import com.google.common.reflect.Parameter; 037import com.google.common.reflect.Reflection; 038import com.google.common.reflect.TypeToken; 039import com.google.common.util.concurrent.AbstractFuture; 040import com.google.errorprone.annotations.CanIgnoreReturnValue; 041import java.lang.annotation.Annotation; 042import java.lang.reflect.AnnotatedType; 043import java.lang.reflect.Constructor; 044import java.lang.reflect.InvocationTargetException; 045import java.lang.reflect.Member; 046import java.lang.reflect.Method; 047import java.lang.reflect.Modifier; 048import java.lang.reflect.ParameterizedType; 049import java.lang.reflect.Type; 050import java.lang.reflect.TypeVariable; 051import java.util.Arrays; 052import java.util.List; 053import java.util.concurrent.ConcurrentMap; 054import junit.framework.Assert; 055import org.jspecify.annotations.NullMarked; 056import org.jspecify.annotations.Nullable; 057 058/** 059 * A test utility that verifies that your methods and constructors throw {@link 060 * NullPointerException} or {@link UnsupportedOperationException} whenever null is passed to a 061 * parameter whose declaration or type isn't annotated with an annotation with the simple name 062 * {@code Nullable}, {@code CheckForNull}, {@code NullableType}, or {@code NullableDecl}. 063 * 064 * <p>The tested methods and constructors are invoked -- each time with one parameter being null and 065 * the rest not null -- and the test fails if no expected exception is thrown. {@code 066 * NullPointerTester} uses best effort to pick non-null default values for many common JDK and Guava 067 * types, and also for interfaces and public classes that have public parameter-less constructors. 068 * When the non-null default value for a particular parameter type cannot be provided by {@code 069 * NullPointerTester}, the caller can provide a custom non-null default value for the parameter type 070 * via {@link #setDefault}. 071 * 072 * @author Kevin Bourrillion 073 * @since 10.0 074 */ 075@GwtIncompatible 076@J2ktIncompatible 077@NullMarked 078public final class NullPointerTester { 079 080 private final ClassToInstanceMap<Object> defaults = MutableClassToInstanceMap.create(); 081 private final List<Member> ignoredMembers = Lists.newArrayList(); 082 083 private ExceptionTypePolicy policy = ExceptionTypePolicy.NPE_OR_UOE; 084 085 /* 086 * Requiring desugaring for guava-*testlib* is likely safe, at least for the reflection-based 087 * NullPointerTester. But if you are a user who is reading this because this change caused you 088 * trouble, please let us know: https://github.com/google/guava/issues/new 089 */ 090 @IgnoreJRERequirement 091 public NullPointerTester() { 092 try { 093 /* 094 * Converter.apply has a non-nullable parameter type but doesn't throw for null arguments. For 095 * more information, see the comments in that class. 096 * 097 * We already know that that's how it behaves, and subclasses of Converter can't change that 098 * behavior. So there's no sense in making all subclass authors exclude the method from any 099 * NullPointerTester tests that they have. 100 */ 101 ignoredMembers.add(Converter.class.getMethod("apply", Object.class)); 102 } catch (NoSuchMethodException shouldBeImpossible) { 103 // Fine: If it doesn't exist, then there's no chance that we're going to be asked to test it. 104 } 105 106 /* 107 * These methods "should" call checkNotNull. However, I'm wary of accidentally introducing 108 * anything that might slow down execution on such a hot path. Given that the methods are only 109 * package-private, I feel OK with just not testing them for NPE. 110 * 111 * Note that testing casValue is particularly dangerous because it uses Unsafe under some 112 * versions of Java, and apparently Unsafe can cause SIGSEGV instead of NPE—almost as if it's 113 * not safe. 114 */ 115 concat( 116 stream(AbstractFuture.class.getDeclaredMethods()), 117 stream(requireNonNull(AbstractFuture.class.getSuperclass()).getDeclaredMethods())) 118 .filter( 119 m -> 120 m.getName().equals("getDoneValue") 121 || m.getName().equals("casValue") 122 || m.getName().equals("casListeners") 123 || m.getName().equals("gasListeners")) 124 .forEach(ignoredMembers::add); 125 } 126 127 /** 128 * Sets a default value that can be used for any parameter of type {@code type}. Returns this 129 * object. 130 */ 131 @CanIgnoreReturnValue 132 public <T> NullPointerTester setDefault(Class<T> type, T value) { 133 defaults.putInstance(type, checkNotNull(value)); 134 return this; 135 } 136 137 /** 138 * Ignore {@code method} in the tests that follow. Returns this object. 139 * 140 * @since 13.0 141 */ 142 @CanIgnoreReturnValue 143 public NullPointerTester ignore(Method method) { 144 ignoredMembers.add(checkNotNull(method)); 145 return this; 146 } 147 148 /** 149 * Ignore {@code constructor} in the tests that follow. Returns this object. 150 * 151 * @since 22.0 152 */ 153 @CanIgnoreReturnValue 154 public NullPointerTester ignore(Constructor<?> constructor) { 155 ignoredMembers.add(checkNotNull(constructor)); 156 return this; 157 } 158 159 /** 160 * Runs {@link #testConstructor} on every constructor in class {@code c} that has at least {@code 161 * minimalVisibility}. 162 */ 163 public void testConstructors(Class<?> c, Visibility minimalVisibility) { 164 for (Constructor<?> constructor : c.getDeclaredConstructors()) { 165 if (minimalVisibility.isVisible(constructor) && !isIgnored(constructor)) { 166 testConstructor(constructor); 167 } 168 } 169 } 170 171 /** Runs {@link #testConstructor} on every public constructor in class {@code c}. */ 172 public void testAllPublicConstructors(Class<?> c) { 173 testConstructors(c, Visibility.PUBLIC); 174 } 175 176 /** 177 * Runs {@link #testMethod} on every static method of class {@code c} that has at least {@code 178 * minimalVisibility}, including those "inherited" from superclasses of the same package. 179 */ 180 public void testStaticMethods(Class<?> c, Visibility minimalVisibility) { 181 for (Method method : minimalVisibility.getStaticMethods(c)) { 182 if (!isIgnored(method)) { 183 testMethod(null, method); 184 } 185 } 186 } 187 188 /** 189 * Runs {@link #testMethod} on every public static method of class {@code c}, including those 190 * "inherited" from superclasses of the same package. 191 */ 192 public void testAllPublicStaticMethods(Class<?> c) { 193 testStaticMethods(c, Visibility.PUBLIC); 194 } 195 196 /** 197 * Runs {@link #testMethod} on every instance method of the class of {@code instance} with at 198 * least {@code minimalVisibility}, including those inherited from superclasses of the same 199 * package. 200 */ 201 public void testInstanceMethods(Object instance, Visibility minimalVisibility) { 202 for (Method method : getInstanceMethodsToTest(instance.getClass(), minimalVisibility)) { 203 testMethod(instance, method); 204 } 205 } 206 207 ImmutableList<Method> getInstanceMethodsToTest(Class<?> c, Visibility minimalVisibility) { 208 ImmutableList.Builder<Method> builder = ImmutableList.builder(); 209 for (Method method : minimalVisibility.getInstanceMethods(c)) { 210 if (!isIgnored(method)) { 211 builder.add(method); 212 } 213 } 214 return builder.build(); 215 } 216 217 /** 218 * Runs {@link #testMethod} on every public instance method of the class of {@code instance}, 219 * including those inherited from superclasses of the same package. 220 */ 221 public void testAllPublicInstanceMethods(Object instance) { 222 testInstanceMethods(instance, Visibility.PUBLIC); 223 } 224 225 /** 226 * Verifies that {@code method} produces a {@link NullPointerException} or {@link 227 * UnsupportedOperationException} whenever <i>any</i> of its non-nullable parameters are null. 228 * 229 * @param instance the instance to invoke {@code method} on, or null if {@code method} is static 230 */ 231 public void testMethod(@Nullable Object instance, Method method) { 232 Class<?>[] types = method.getParameterTypes(); 233 for (int nullIndex = 0; nullIndex < types.length; nullIndex++) { 234 testMethodParameter(instance, method, nullIndex); 235 } 236 } 237 238 /** 239 * Verifies that {@code ctor} produces a {@link NullPointerException} or {@link 240 * UnsupportedOperationException} whenever <i>any</i> of its non-nullable parameters are null. 241 */ 242 public void testConstructor(Constructor<?> ctor) { 243 Class<?> declaringClass = ctor.getDeclaringClass(); 244 checkArgument( 245 Modifier.isStatic(declaringClass.getModifiers()) 246 || declaringClass.getEnclosingClass() == null, 247 "Cannot test constructor of non-static inner class: %s", 248 declaringClass.getName()); 249 Class<?>[] types = ctor.getParameterTypes(); 250 for (int nullIndex = 0; nullIndex < types.length; nullIndex++) { 251 testConstructorParameter(ctor, nullIndex); 252 } 253 } 254 255 /** 256 * Verifies that {@code method} produces a {@link NullPointerException} or {@link 257 * UnsupportedOperationException} when the parameter in position {@code paramIndex} is null. If 258 * this parameter is marked nullable, this method does nothing. 259 * 260 * @param instance the instance to invoke {@code method} on, or null if {@code method} is static 261 */ 262 public void testMethodParameter(@Nullable Object instance, Method method, int paramIndex) { 263 method.setAccessible(true); 264 testParameter(instance, invokable(instance, method), paramIndex, method.getDeclaringClass()); 265 } 266 267 /** 268 * Verifies that {@code ctor} produces a {@link NullPointerException} or {@link 269 * UnsupportedOperationException} when the parameter in position {@code paramIndex} is null. If 270 * this parameter is marked nullable, this method does nothing. 271 */ 272 public void testConstructorParameter(Constructor<?> ctor, int paramIndex) { 273 ctor.setAccessible(true); 274 testParameter(null, Invokable.from(ctor), paramIndex, ctor.getDeclaringClass()); 275 } 276 277 /** Visibility of any method or constructor. */ 278 public enum Visibility { 279 PACKAGE { 280 @Override 281 boolean isVisible(int modifiers) { 282 return !Modifier.isPrivate(modifiers); 283 } 284 }, 285 286 PROTECTED { 287 @Override 288 boolean isVisible(int modifiers) { 289 return Modifier.isPublic(modifiers) || Modifier.isProtected(modifiers); 290 } 291 }, 292 293 PUBLIC { 294 @Override 295 boolean isVisible(int modifiers) { 296 return Modifier.isPublic(modifiers); 297 } 298 }; 299 300 abstract boolean isVisible(int modifiers); 301 302 /** Returns {@code true} if {@code member} is visible under {@code this} visibility. */ 303 final boolean isVisible(Member member) { 304 return isVisible(member.getModifiers()); 305 } 306 307 final Iterable<Method> getStaticMethods(Class<?> cls) { 308 ImmutableList.Builder<Method> builder = ImmutableList.builder(); 309 for (Method method : getVisibleMethods(cls)) { 310 if (Invokable.from(method).isStatic()) { 311 builder.add(method); 312 } 313 } 314 return builder.build(); 315 } 316 317 final Iterable<Method> getInstanceMethods(Class<?> cls) { 318 ConcurrentMap<Signature, Method> map = Maps.newConcurrentMap(); 319 for (Method method : getVisibleMethods(cls)) { 320 if (!Invokable.from(method).isStatic()) { 321 map.putIfAbsent(new Signature(method), method); 322 } 323 } 324 return map.values(); 325 } 326 327 private ImmutableList<Method> getVisibleMethods(Class<?> cls) { 328 // Don't use cls.getPackage() because it does nasty things like reading 329 // a file. 330 String visiblePackage = Reflection.getPackageName(cls); 331 ImmutableList.Builder<Method> builder = ImmutableList.builder(); 332 for (Class<?> type : TypeToken.of(cls).getTypes().rawTypes()) { 333 if (!Reflection.getPackageName(type).equals(visiblePackage)) { 334 break; 335 } 336 for (Method method : type.getDeclaredMethods()) { 337 if (!method.isSynthetic() && isVisible(method)) { 338 builder.add(method); 339 } 340 } 341 } 342 return builder.build(); 343 } 344 } 345 346 private static final class Signature { 347 private final String name; 348 private final ImmutableList<Class<?>> parameterTypes; 349 350 Signature(Method method) { 351 this(method.getName(), ImmutableList.copyOf(method.getParameterTypes())); 352 } 353 354 Signature(String name, ImmutableList<Class<?>> parameterTypes) { 355 this.name = name; 356 this.parameterTypes = parameterTypes; 357 } 358 359 @Override 360 public boolean equals(@Nullable Object obj) { 361 if (obj instanceof Signature) { 362 Signature that = (Signature) obj; 363 return name.equals(that.name) && parameterTypes.equals(that.parameterTypes); 364 } 365 return false; 366 } 367 368 @Override 369 public int hashCode() { 370 return Objects.hashCode(name, parameterTypes); 371 } 372 } 373 374 /** 375 * Verifies that {@code invokable} produces a {@link NullPointerException} or {@link 376 * UnsupportedOperationException} when the parameter in position {@code paramIndex} is null. If 377 * this parameter is marked nullable, this method does nothing. 378 * 379 * @param instance the instance to invoke {@code invokable} on, or null if {@code invokable} is 380 * static 381 */ 382 private void testParameter( 383 @Nullable Object instance, Invokable<?, ?> invokable, int paramIndex, Class<?> testedClass) { 384 if (isPrimitiveOrNullable(invokable.getParameters().get(paramIndex))) { 385 return; // there's nothing to test 386 } 387 @Nullable Object[] params = buildParamList(invokable, paramIndex); 388 try { 389 @SuppressWarnings("unchecked") // We'll get a runtime exception if the type is wrong. 390 Invokable<Object, ?> unsafe = (Invokable<Object, ?>) invokable; 391 unsafe.invoke(instance, params); 392 Assert.fail( 393 "No exception thrown for parameter at index " 394 + paramIndex 395 + " from " 396 + invokable 397 + Arrays.toString(params) 398 + " for " 399 + testedClass); 400 } catch (InvocationTargetException e) { 401 Throwable cause = e.getCause(); 402 if (policy.isExpectedType(cause)) { 403 return; 404 } 405 throw new AssertionError( 406 String.format( 407 "wrong exception thrown from %s when passing null to %s parameter at index %s.%n" 408 + "Full parameters: %s%n" 409 + "Actual exception message: %s", 410 invokable, 411 invokable.getParameters().get(paramIndex).getType(), 412 paramIndex, 413 Arrays.toString(params), 414 cause), 415 cause); 416 } catch (IllegalAccessException e) { 417 throw new RuntimeException(e); 418 } 419 } 420 421 private @Nullable Object[] buildParamList( 422 Invokable<?, ?> invokable, int indexOfParamToSetToNull) { 423 ImmutableList<Parameter> params = invokable.getParameters(); 424 @Nullable Object[] args = new Object[params.size()]; 425 426 for (int i = 0; i < args.length; i++) { 427 Parameter param = params.get(i); 428 if (i != indexOfParamToSetToNull) { 429 args[i] = getDefaultValue(param.getType()); 430 Assert.assertTrue( 431 "Can't find or create a sample instance for type '" 432 + param.getType() 433 + "'; please provide one using NullPointerTester.setDefault()", 434 args[i] != null || isNullable(param)); 435 } 436 } 437 return args; 438 } 439 440 private <T> @Nullable T getDefaultValue(TypeToken<T> type) { 441 // We assume that all defaults are generics-safe, even if they aren't, 442 // we take the risk. 443 @SuppressWarnings("unchecked") 444 T defaultValue = (T) defaults.getInstance(type.getRawType()); 445 if (defaultValue != null) { 446 return defaultValue; 447 } 448 @SuppressWarnings("unchecked") // All arbitrary instances are generics-safe 449 T arbitrary = (T) ArbitraryInstances.get(type.getRawType()); 450 if (arbitrary != null) { 451 return arbitrary; 452 } 453 if (type.getRawType() == Class.class) { 454 // If parameter is Class<? extends Foo>, we return Foo.class 455 @SuppressWarnings("unchecked") 456 T defaultClass = (T) getFirstTypeParameter(type.getType()).getRawType(); 457 return defaultClass; 458 } 459 if (type.getRawType() == TypeToken.class) { 460 // If parameter is TypeToken<? extends Foo>, we return TypeToken<Foo>. 461 @SuppressWarnings("unchecked") 462 T defaultType = (T) getFirstTypeParameter(type.getType()); 463 return defaultType; 464 } 465 if (type.getRawType() == Converter.class) { 466 TypeToken<?> convertFromType = type.resolveType(Converter.class.getTypeParameters()[0]); 467 TypeToken<?> convertToType = type.resolveType(Converter.class.getTypeParameters()[1]); 468 @SuppressWarnings("unchecked") // returns default for both F and T 469 T defaultConverter = (T) defaultConverter(convertFromType, convertToType); 470 return defaultConverter; 471 } 472 if (type.getRawType().isInterface()) { 473 return newDefaultReturningProxy(type); 474 } 475 return null; 476 } 477 478 private <F, T> Converter<F, T> defaultConverter( 479 TypeToken<F> convertFromType, TypeToken<T> convertToType) { 480 return new Converter<F, T>() { 481 @Override 482 protected T doForward(F a) { 483 return doConvert(convertToType); 484 } 485 486 @Override 487 protected F doBackward(T b) { 488 return doConvert(convertFromType); 489 } 490 491 private /*static*/ <S> S doConvert(TypeToken<S> type) { 492 return checkNotNull(getDefaultValue(type)); 493 } 494 }; 495 } 496 497 private static TypeToken<?> getFirstTypeParameter(Type type) { 498 if (type instanceof ParameterizedType) { 499 return TypeToken.of(((ParameterizedType) type).getActualTypeArguments()[0]); 500 } else { 501 return TypeToken.of(Object.class); 502 } 503 } 504 505 private <T> T newDefaultReturningProxy(TypeToken<T> type) { 506 return new DummyProxy() { 507 @Override 508 <R> @Nullable R dummyReturnValue(TypeToken<R> returnType) { 509 return getDefaultValue(returnType); 510 } 511 }.newProxy(type); 512 } 513 514 private static Invokable<?, ?> invokable(@Nullable Object instance, Method method) { 515 if (instance == null) { 516 return Invokable.from(method); 517 } else { 518 return TypeToken.of(instance.getClass()).method(method); 519 } 520 } 521 522 static boolean isPrimitiveOrNullable(Parameter param) { 523 return param.getType().getRawType().isPrimitive() || isNullable(param); 524 } 525 526 private static final ImmutableSet<String> NULLABLE_ANNOTATION_SIMPLE_NAMES = 527 ImmutableSet.of("CheckForNull", "Nullable", "NullableDecl", "NullableType"); 528 529 static boolean isNullable(Invokable<?, ?> invokable) { 530 return NULLNESS_ANNOTATION_READER.isNullable(invokable); 531 } 532 533 static boolean isNullable(Parameter param) { 534 return NULLNESS_ANNOTATION_READER.isNullable(param); 535 } 536 537 private static boolean containsNullable(Annotation[] annotations) { 538 for (Annotation annotation : annotations) { 539 if (NULLABLE_ANNOTATION_SIMPLE_NAMES.contains(annotation.annotationType().getSimpleName())) { 540 return true; 541 } 542 } 543 return false; 544 } 545 546 private boolean isIgnored(Member member) { 547 return member.isSynthetic() || ignoredMembers.contains(member) || isEquals(member); 548 } 549 550 /** 551 * Returns true if the given member is a method that overrides {@link Object#equals(Object)}. 552 * 553 * <p>The documentation for {@link Object#equals} says it should accept null, so don't require an 554 * explicit {@code @Nullable} annotation (see <a 555 * href="https://github.com/google/guava/issues/1819">#1819</a>). 556 * 557 * <p>It is not necessary to consider visibility, return type, or type parameter declarations. The 558 * declaration of a method with the same name and formal parameters as {@link Object#equals} that 559 * is not public and boolean-returning, or that declares any type parameters, would be rejected at 560 * compile-time. 561 */ 562 private static boolean isEquals(Member member) { 563 if (!(member instanceof Method)) { 564 return false; 565 } 566 Method method = (Method) member; 567 if (!method.getName().contentEquals("equals")) { 568 return false; 569 } 570 Class<?>[] parameters = method.getParameterTypes(); 571 if (parameters.length != 1) { 572 return false; 573 } 574 if (!parameters[0].equals(Object.class)) { 575 return false; 576 } 577 return true; 578 } 579 580 /** Strategy for exception type matching used by {@link NullPointerTester}. */ 581 private enum ExceptionTypePolicy { 582 583 /** 584 * Exceptions should be {@link NullPointerException} or {@link UnsupportedOperationException}. 585 */ 586 NPE_OR_UOE() { 587 @Override 588 public boolean isExpectedType(Throwable cause) { 589 return cause instanceof NullPointerException 590 || cause instanceof UnsupportedOperationException; 591 } 592 }, 593 594 /** 595 * Exceptions should be {@link NullPointerException}, {@link IllegalArgumentException}, or 596 * {@link UnsupportedOperationException}. 597 */ 598 NPE_IAE_OR_UOE() { 599 @Override 600 public boolean isExpectedType(Throwable cause) { 601 return cause instanceof NullPointerException 602 || cause instanceof IllegalArgumentException 603 || cause instanceof UnsupportedOperationException; 604 } 605 }; 606 607 public abstract boolean isExpectedType(Throwable cause); 608 } 609 610 private static boolean annotatedTypeExists() { 611 try { 612 Class.forName("java.lang.reflect.AnnotatedType"); 613 } catch (ClassNotFoundException e) { 614 return false; 615 } 616 return true; 617 } 618 619 private static final NullnessAnnotationReader NULLNESS_ANNOTATION_READER = 620 annotatedTypeExists() 621 ? NullnessAnnotationReader.FROM_DECLARATION_AND_TYPE_USE_ANNOTATIONS 622 : NullnessAnnotationReader.FROM_DECLARATION_ANNOTATIONS_ONLY; 623 624 /** 625 * Looks for declaration nullness annotations and, if supported, type-use nullness annotations. 626 * 627 * <p>Under Android VMs, the methods for retrieving type-use annotations don't exist. This means 628 * that {@link NullPointerTester} may misbehave under Android when used on classes that rely on 629 * type-use annotations. 630 * 631 * <p>Under j2objc, the necessary APIs exist, but some (perhaps all) return stub values, like 632 * empty arrays. Presumably {@link NullPointerTester} could likewise misbehave under j2objc, but I 633 * don't know that anyone uses it there, anyway. 634 */ 635 private enum NullnessAnnotationReader { 636 @SuppressWarnings("Java7ApiChecker") 637 FROM_DECLARATION_AND_TYPE_USE_ANNOTATIONS { 638 @Override 639 boolean isNullable(Invokable<?, ?> invokable) { 640 return FROM_DECLARATION_ANNOTATIONS_ONLY.isNullable(invokable) 641 || containsNullable(invokable.getAnnotatedReturnType().getAnnotations()); 642 // TODO(cpovirk): Should we also check isNullableTypeVariable? 643 } 644 645 @Override 646 boolean isNullable(Parameter param) { 647 return FROM_DECLARATION_ANNOTATIONS_ONLY.isNullable(param) 648 || containsNullable(param.getAnnotatedType().getAnnotations()) 649 || isNullableTypeVariable(param.getAnnotatedType().getType()); 650 } 651 652 boolean isNullableTypeVariable(Type type) { 653 if (!(type instanceof TypeVariable)) { 654 return false; 655 } 656 TypeVariable<?> typeVar = (TypeVariable<?>) type; 657 for (AnnotatedType bound : typeVar.getAnnotatedBounds()) { 658 // Until Java 15, the isNullableTypeVariable case here won't help: 659 // https://bugs.openjdk.org/browse/JDK-8202469 660 if (containsNullable(bound.getAnnotations()) || isNullableTypeVariable(bound.getType())) { 661 return true; 662 } 663 } 664 return false; 665 } 666 }, 667 FROM_DECLARATION_ANNOTATIONS_ONLY { 668 @Override 669 boolean isNullable(Invokable<?, ?> invokable) { 670 return containsNullable(invokable.getAnnotations()); 671 } 672 673 @Override 674 boolean isNullable(Parameter param) { 675 return containsNullable(param.getAnnotations()); 676 } 677 }; 678 679 abstract boolean isNullable(Invokable<?, ?> invokable); 680 681 abstract boolean isNullable(Parameter param); 682 } 683}