diff --git a/src/main/java/org/openrewrite/staticanalysis/AnnotateNullableParameters.java b/src/main/java/org/openrewrite/staticanalysis/AnnotateNullableParameters.java index c2848c8605..54c5a02f46 100644 --- a/src/main/java/org/openrewrite/staticanalysis/AnnotateNullableParameters.java +++ b/src/main/java/org/openrewrite/staticanalysis/AnnotateNullableParameters.java @@ -167,6 +167,8 @@ private Map buildIdentifierMap(List *
  • Direct null comparisons (param == null, param != null)
  • *
  • Known null-checking method calls (Objects.isNull, StringUtils.isBlank, etc.)
  • + *
  • Methods that provide default values for null parameters (Objects.requireNonNullElse, Objects.requireNonNullElseGet)
  • + *
  • Methods that handle nullable values (Optional.ofNullable)
  • *
  • Negated null-checking method calls (!Objects.isNull, !StringUtils.isBlank, etc.)
  • * */ @@ -175,6 +177,9 @@ private static class NullCheckVisitor extends JavaIsoVisitor> new MethodMatcher("com.google.common.base.Strings isNullOrEmpty(..)"), // Guava new MethodMatcher("java.util.Objects isNull(..)"), new MethodMatcher("java.util.Objects nonNull(..)"), + new MethodMatcher("java.util.Objects requireNonNullElse(..)"), // Provides default for null + new MethodMatcher("java.util.Objects requireNonNullElseGet(..)"), // Provides default for null + new MethodMatcher("java.util.Optional ofNullable(..)"), // Handles nullable values new MethodMatcher("org.apache.commons.lang3.StringUtils isBlank(..)"), new MethodMatcher("org.apache.commons.lang3.StringUtils isEmpty(..)"), new MethodMatcher("org.apache.commons.lang3.StringUtils isNotBlank(..)"), @@ -208,6 +213,18 @@ public J.If visitIf(J.If iff, Set nullCheckedParams) { return iff; } + @Override + public J.MethodInvocation visitMethodInvocation(J.MethodInvocation method, Set nullCheckedParams) { + // Handle standalone Objects.requireNonNull calls (not just in if conditions) + if (isKnownNullMethodChecker(method) && method.getArguments().get(0) instanceof J.Identifier) { + J.Identifier firstArgument = (J.Identifier) method.getArguments().get(0); + if (containsIdentifierByName(identifiers, firstArgument)) { + nullCheckedParams.add(firstArgument); + } + } + return super.visitMethodInvocation(method, nullCheckedParams); + } + private void handleCondition(Expression condition, Set nullCheckedParams) { if (condition instanceof J.Binary) { handleBinary((J.Binary) condition, nullCheckedParams); diff --git a/src/test/java/org/openrewrite/staticanalysis/AnnotateNullableParametersTest.java b/src/test/java/org/openrewrite/staticanalysis/AnnotateNullableParametersTest.java index de2da4af69..a87c565bfc 100644 --- a/src/test/java/org/openrewrite/staticanalysis/AnnotateNullableParametersTest.java +++ b/src/test/java/org/openrewrite/staticanalysis/AnnotateNullableParametersTest.java @@ -378,6 +378,116 @@ boolean bar(Object obj) { @Nested class KnownNullCheckers { + @Test + void objectsRequireNonNullElse() { + rewriteRun( + //language=java + java( + """ + import java.util.Objects; + + class PersonBuilder { + private String name; + private String email; + + public PersonBuilder setName(String name) { + this.name = Objects.requireNonNullElse(name, "Unknown"); + return this; + } + + public PersonBuilder setNameWithFallback(String name, String fallback) { + this.name = Objects.requireNonNullElse(name, fallback); + return this; + } + + public PersonBuilder setEmail(String email) { + this.email = Objects.requireNonNullElseGet(email, () -> "default@example.com"); + return this; + } + } + """, + """ + import org.jspecify.annotations.Nullable; + + import java.util.Objects; + + class PersonBuilder { + private String name; + private String email; + + public PersonBuilder setName(@Nullable String name) { + this.name = Objects.requireNonNullElse(name, "Unknown"); + return this; + } + + public PersonBuilder setNameWithFallback(@Nullable String name, String fallback) { + this.name = Objects.requireNonNullElse(name, fallback); + return this; + } + + public PersonBuilder setEmail(@Nullable String email) { + this.email = Objects.requireNonNullElseGet(email, () -> "default@example.com"); + return this; + } + } + """ + ) + ); + } + + @Test + void optionalOfNullable() { + rewriteRun( + //language=java + java( + """ + import java.util.Optional; + + public class PersonService { + + public Optional processValue(String value) { + return Optional.ofNullable(value) + .filter(v -> !v.isEmpty()) + .map(String::toUpperCase); + } + + public Optional wrapValue(String input) { + // Direct usage of parameter in Optional.ofNullable + return Optional.ofNullable(input); + } + + public void conditionalWrap(String data) { + Optional.ofNullable(data).ifPresent(d -> System.out.println(d)); + } + } + """, + """ + import org.jspecify.annotations.Nullable; + + import java.util.Optional; + + public class PersonService { + + public Optional processValue(@Nullable String value) { + return Optional.ofNullable(value) + .filter(v -> !v.isEmpty()) + .map(String::toUpperCase); + } + + public Optional wrapValue(@Nullable String input) { + // Direct usage of parameter in Optional.ofNullable + return Optional.ofNullable(input); + } + + public void conditionalWrap(@Nullable String data) { + Optional.ofNullable(data).ifPresent(d -> System.out.println(d)); + } + } + """ + ) + ); + } + @CsvSource({ "java.util.Objects, Objects.nonNull", "org.apache.commons.lang3.StringUtils, StringUtils.isNotBlank",