Introduce null-safety of Spring Framework API
This commit introduces 2 new @Nullable and @NonNullApi annotations that leverage JSR 305 (dormant but available via Findbugs jsr305 dependency and already used by libraries like OkHttp) meta-annotations to specify explicitly null-safety of Spring Framework parameters and return values. In order to avoid adding too much annotations, the default is set at package level with @NonNullApi and @Nullable annotations are added when needed at parameter or return value level. These annotations are intended to be used on Spring Framework itself but also by other Spring projects. @Nullable annotations have been introduced based on Javadoc and search of patterns like "return null;". It is expected that nullability of Spring Framework API will be polished with complementary commits. In practice, this will make the whole Spring Framework API null-safe for Kotlin projects (when KT-10942 will be fixed) since Kotlin will be able to leverage these annotations to know if a parameter or a return value is nullable or not. But this is also useful for Java developers as well since IntelliJ IDEA, for example, also understands these annotations to generate warnings when unsafe nullable usages are detected. Issue: SPR-15540
This commit is contained in:
@@ -19,6 +19,7 @@ package org.springframework.expression;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.core.convert.TypeDescriptor;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
/**
|
||||
* A constructor resolver attempts locate a constructor and returns a ConstructorExecutor
|
||||
@@ -40,6 +41,7 @@ public interface ConstructorResolver {
|
||||
* @param argumentTypes the arguments that the constructor must be able to handle
|
||||
* @return a ConstructorExecutor that can invoke the constructor, or null if non found
|
||||
*/
|
||||
@Nullable
|
||||
ConstructorExecutor resolve(EvaluationContext context, String typeName, List<TypeDescriptor> argumentTypes)
|
||||
throws AccessException;
|
||||
|
||||
|
||||
@@ -19,6 +19,7 @@ package org.springframework.expression;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.core.convert.TypeDescriptor;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
/**
|
||||
* A method resolver attempts locate a method and returns a command executor that can be
|
||||
@@ -39,6 +40,7 @@ public interface MethodResolver {
|
||||
* @param argumentTypes the arguments that the constructor must be able to handle
|
||||
* @return a MethodExecutor that can invoke the method, or null if the method cannot be found
|
||||
*/
|
||||
@Nullable
|
||||
MethodExecutor resolve(EvaluationContext context, Object targetObject, String name,
|
||||
List<TypeDescriptor> argumentTypes) throws AccessException;
|
||||
|
||||
|
||||
@@ -17,6 +17,8 @@
|
||||
package org.springframework.expression;
|
||||
|
||||
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
/**
|
||||
* A property accessor is able to read from (and possibly write to) an object's properties.
|
||||
* This interface places no restrictions, and so implementors are free to access properties
|
||||
@@ -43,6 +45,7 @@ public interface PropertyAccessor {
|
||||
* @return an array of classes that this resolver is suitable for
|
||||
* (or {@code null} if a general resolver)
|
||||
*/
|
||||
@Nullable
|
||||
Class<?>[] getSpecificTargetClasses();
|
||||
|
||||
/**
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
package org.springframework.expression;
|
||||
|
||||
import org.springframework.core.convert.TypeDescriptor;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
/**
|
||||
* A type converter can convert values between different types encountered during
|
||||
@@ -53,6 +54,7 @@ public interface TypeConverter {
|
||||
* @return the converted value
|
||||
* @throws EvaluationException if conversion failed or is not possible to begin with
|
||||
*/
|
||||
@Nullable
|
||||
Object convertValue(Object value, TypeDescriptor sourceType, TypeDescriptor targetType);
|
||||
|
||||
}
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
/**
|
||||
* Common utility classes behind the <em>Spring Expression Language</em>.
|
||||
*/
|
||||
@NonNullApi
|
||||
package org.springframework.expression.common;
|
||||
|
||||
import org.springframework.lang.NonNullApi;
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
/**
|
||||
* Core abstractions behind the <em>Spring Expression Language</em>.
|
||||
*/
|
||||
@NonNullApi
|
||||
package org.springframework.expression;
|
||||
|
||||
import org.springframework.lang.NonNullApi;
|
||||
|
||||
@@ -25,6 +25,7 @@ import java.util.Stack;
|
||||
import org.springframework.asm.ClassWriter;
|
||||
import org.springframework.asm.MethodVisitor;
|
||||
import org.springframework.asm.Opcodes;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
@@ -148,6 +149,7 @@ public class CodeFlow implements Opcodes {
|
||||
/**
|
||||
* Return the descriptor for the item currently on top of the stack (in the current scope).
|
||||
*/
|
||||
@Nullable
|
||||
public String lastDescriptor() {
|
||||
if (this.compilationScopes.peek().isEmpty()) {
|
||||
return null;
|
||||
@@ -508,7 +510,8 @@ public class CodeFlow implements Opcodes {
|
||||
* @return the type descriptor for the object
|
||||
* (descriptor is "Ljava/lang/Object" for {@code null} value)
|
||||
*/
|
||||
public static String toDescriptorFromObject(Object value) {
|
||||
@Nullable
|
||||
public static String toDescriptorFromObject(@Nullable Object value) {
|
||||
if (value == null) {
|
||||
return "Ljava/lang/Object";
|
||||
}
|
||||
@@ -768,6 +771,7 @@ public class CodeFlow implements Opcodes {
|
||||
* @param type the type (may be primitive) for which to determine the descriptor
|
||||
* @return the descriptor
|
||||
*/
|
||||
@Nullable
|
||||
public static String toDescriptor(Class<?> type) {
|
||||
String name = type.getName();
|
||||
if (type.isPrimitive()) {
|
||||
|
||||
@@ -31,6 +31,7 @@ import org.springframework.expression.PropertyAccessor;
|
||||
import org.springframework.expression.TypeComparator;
|
||||
import org.springframework.expression.TypeConverter;
|
||||
import org.springframework.expression.TypedValue;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
@@ -204,6 +205,7 @@ public class ExpressionState {
|
||||
this.variableScopes.peek().setVariable(name, value);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Object lookupLocalVariable(String name) {
|
||||
ensureVariableScopesInitialized();
|
||||
int scopeNumber = this.variableScopes.size() - 1;
|
||||
|
||||
@@ -18,6 +18,7 @@ package org.springframework.expression.spel;
|
||||
|
||||
import org.springframework.expression.EvaluationException;
|
||||
import org.springframework.expression.TypedValue;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
/**
|
||||
* Represents a node in the Ast for a parsed expression.
|
||||
@@ -85,6 +86,7 @@ public interface SpelNode {
|
||||
* @return the class of the object if it is not already a class object,
|
||||
* or {@code null} if the object is {@code null}
|
||||
*/
|
||||
@Nullable
|
||||
Class<?> getObjectClass(Object obj);
|
||||
|
||||
/**
|
||||
|
||||
@@ -39,6 +39,7 @@ import org.springframework.expression.spel.SpelEvaluationException;
|
||||
import org.springframework.expression.spel.SpelMessage;
|
||||
import org.springframework.expression.spel.SpelNode;
|
||||
import org.springframework.expression.spel.support.ReflectiveConstructorExecutor;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
/**
|
||||
* Represents the invocation of a constructor. Either a constructor on a regular type or
|
||||
@@ -178,6 +179,7 @@ public class ConstructorReference extends SpelNodeImpl {
|
||||
* @return a reusable ConstructorExecutor that can be invoked to run the constructor or null
|
||||
* @throws SpelEvaluationException if there is a problem locating the constructor
|
||||
*/
|
||||
@Nullable
|
||||
private ConstructorExecutor findExecutorForConstructor(String typeName,
|
||||
List<TypeDescriptor> argumentTypes, ExpressionState state)
|
||||
throws SpelEvaluationException {
|
||||
|
||||
@@ -38,6 +38,7 @@ import org.springframework.expression.spel.SpelEvaluationException;
|
||||
import org.springframework.expression.spel.SpelMessage;
|
||||
import org.springframework.expression.spel.support.ReflectiveMethodExecutor;
|
||||
import org.springframework.expression.spel.support.ReflectiveMethodResolver;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
/**
|
||||
* Expression language AST node that represents a method reference.
|
||||
@@ -168,6 +169,7 @@ public class MethodReference extends SpelNodeImpl {
|
||||
return Collections.unmodifiableList(descriptors);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private MethodExecutor getCachedExecutor(EvaluationContext evaluationContext, Object value,
|
||||
TypeDescriptor target, List<TypeDescriptor> argumentTypes) {
|
||||
|
||||
|
||||
@@ -23,6 +23,7 @@ import org.springframework.asm.Label;
|
||||
import org.springframework.asm.MethodVisitor;
|
||||
import org.springframework.expression.EvaluationContext;
|
||||
import org.springframework.expression.spel.CodeFlow;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.ClassUtils;
|
||||
import org.springframework.util.NumberUtils;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
@@ -61,6 +62,7 @@ public abstract class Operator extends SpelNodeImpl {
|
||||
return this.children[0];
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public SpelNodeImpl getRightOperand() {
|
||||
return this.children[1];
|
||||
}
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
/**
|
||||
* SpEL's abstract syntax tree.
|
||||
*/
|
||||
@NonNullApi
|
||||
package org.springframework.expression.spel.ast;
|
||||
|
||||
import org.springframework.lang.NonNullApi;
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
/**
|
||||
* SpEL's central implementation package.
|
||||
*/
|
||||
@NonNullApi
|
||||
package org.springframework.expression.spel;
|
||||
|
||||
import org.springframework.lang.NonNullApi;
|
||||
|
||||
@@ -72,6 +72,7 @@ import org.springframework.expression.spel.ast.StringLiteral;
|
||||
import org.springframework.expression.spel.ast.Ternary;
|
||||
import org.springframework.expression.spel.ast.TypeReference;
|
||||
import org.springframework.expression.spel.ast.VariableReference;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
@@ -374,6 +375,7 @@ class InternalSpelExpressionParser extends TemplateAwareExpressionParser {
|
||||
}
|
||||
|
||||
// nonDottedNode: indexer;
|
||||
@Nullable
|
||||
private SpelNodeImpl maybeEatNonDottedNode() {
|
||||
if (peekToken(TokenKind.LSQUARE)) {
|
||||
if (maybeEatIndexer()) {
|
||||
@@ -392,6 +394,7 @@ class InternalSpelExpressionParser extends TemplateAwareExpressionParser {
|
||||
// | lastSelection
|
||||
// ))
|
||||
// ;
|
||||
@Nullable
|
||||
private SpelNodeImpl eatDottedNode() {
|
||||
Token t = nextToken(); // it was a '.' or a '?.'
|
||||
boolean nullSafeNavigation = (t.kind == TokenKind.SAFE_NAVI);
|
||||
@@ -434,6 +437,7 @@ class InternalSpelExpressionParser extends TemplateAwareExpressionParser {
|
||||
}
|
||||
|
||||
// methodArgs : LPAREN! (argument (COMMA! argument)* (COMMA!)?)? RPAREN!;
|
||||
@Nullable
|
||||
private SpelNodeImpl[] maybeEatMethodArgs() {
|
||||
if (!peekToken(TokenKind.LPAREN)) {
|
||||
return null;
|
||||
@@ -497,6 +501,7 @@ class InternalSpelExpressionParser extends TemplateAwareExpressionParser {
|
||||
// | lastSelection
|
||||
// | indexer
|
||||
// | constructor
|
||||
@Nullable
|
||||
private SpelNodeImpl eatStartNode() {
|
||||
if (maybeEatLiteral()) {
|
||||
return pop();
|
||||
@@ -888,6 +893,7 @@ class InternalSpelExpressionParser extends TemplateAwareExpressionParser {
|
||||
// relationalOperator
|
||||
// : EQUAL | NOT_EQUAL | LESS_THAN | LESS_THAN_OR_EQUAL | GREATER_THAN
|
||||
// | GREATER_THAN_OR_EQUAL | INSTANCEOF | BETWEEN | MATCHES
|
||||
@Nullable
|
||||
private Token maybeEatRelationalOperator() {
|
||||
Token t = peekToken();
|
||||
if (t == null) {
|
||||
@@ -988,6 +994,7 @@ class InternalSpelExpressionParser extends TemplateAwareExpressionParser {
|
||||
return this.tokenStreamPointer<this.tokenStream.size();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private Token nextToken() {
|
||||
if (this.tokenStreamPointer >= this.tokenStreamLength) {
|
||||
return null;
|
||||
@@ -995,6 +1002,7 @@ class InternalSpelExpressionParser extends TemplateAwareExpressionParser {
|
||||
return this.tokenStream.get(this.tokenStreamPointer++);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private Token peekToken() {
|
||||
if (this.tokenStreamPointer >= this.tokenStreamLength) {
|
||||
return null;
|
||||
|
||||
@@ -35,6 +35,7 @@ import org.springframework.expression.spel.CodeFlow;
|
||||
import org.springframework.expression.spel.CompiledExpression;
|
||||
import org.springframework.expression.spel.SpelParserConfiguration;
|
||||
import org.springframework.expression.spel.ast.SpelNodeImpl;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.ClassUtils;
|
||||
import org.springframework.util.ConcurrentReferenceHashMap;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
@@ -97,6 +98,7 @@ public class SpelCompiler implements Opcodes {
|
||||
* @return an instance of the class implementing the compiled expression, or null
|
||||
* if compilation is not possible
|
||||
*/
|
||||
@Nullable
|
||||
public CompiledExpression compile(SpelNodeImpl expression) {
|
||||
if (expression.isCompilable()) {
|
||||
if (logger.isDebugEnabled()) {
|
||||
@@ -130,6 +132,7 @@ public class SpelCompiler implements Opcodes {
|
||||
* @return the expression call, or {@code null} if the decision was to opt out of
|
||||
* compilation during code generation
|
||||
*/
|
||||
@Nullable
|
||||
private Class<? extends CompiledExpression> createExpressionClass(SpelNodeImpl expressionToCompile) {
|
||||
// Create class outline 'spel/ExNNN extends org.springframework.expression.spel.CompiledExpression'
|
||||
String clazzName = "spel/Ex" + getNextSuffix();
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
/**
|
||||
* SpEL's standard parser implementation.
|
||||
*/
|
||||
@NonNullApi
|
||||
package org.springframework.expression.spel.standard;
|
||||
|
||||
import org.springframework.lang.NonNullApi;
|
||||
|
||||
@@ -26,6 +26,7 @@ import org.springframework.core.convert.TypeDescriptor;
|
||||
import org.springframework.expression.EvaluationException;
|
||||
import org.springframework.expression.TypeConverter;
|
||||
import org.springframework.expression.spel.SpelEvaluationException;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ClassUtils;
|
||||
import org.springframework.util.MethodInvoker;
|
||||
@@ -50,6 +51,7 @@ public class ReflectionHelper {
|
||||
* @return a MatchInfo object indicating what kind of match it was,
|
||||
* or {@code null} if it was not a match
|
||||
*/
|
||||
@Nullable
|
||||
static ArgumentsMatchInfo compareArguments(
|
||||
List<TypeDescriptor> expectedArgTypes, List<TypeDescriptor> suppliedArgTypes, TypeConverter typeConverter) {
|
||||
|
||||
@@ -139,6 +141,7 @@ public class ReflectionHelper {
|
||||
* @return a MatchInfo object indicating what kind of match it was,
|
||||
* or {@code null} if it was not a match
|
||||
*/
|
||||
@Nullable
|
||||
static ArgumentsMatchInfo compareArgumentsVarargs(
|
||||
List<TypeDescriptor> expectedArgTypes, List<TypeDescriptor> suppliedArgTypes, TypeConverter typeConverter) {
|
||||
|
||||
@@ -257,8 +260,9 @@ public class ReflectionHelper {
|
||||
* @return {@code true} if some kind of conversion occurred on an argument
|
||||
* @throws EvaluationException if a problem occurs during conversion
|
||||
*/
|
||||
@Nullable
|
||||
static boolean convertArguments(TypeConverter converter, Object[] arguments, Executable executable,
|
||||
Integer varargsPosition) throws EvaluationException {
|
||||
@Nullable Integer varargsPosition) throws EvaluationException {
|
||||
|
||||
boolean conversionOccurred = false;
|
||||
if (varargsPosition == null) {
|
||||
|
||||
@@ -25,6 +25,7 @@ import org.springframework.expression.AccessException;
|
||||
import org.springframework.expression.EvaluationContext;
|
||||
import org.springframework.expression.MethodExecutor;
|
||||
import org.springframework.expression.TypedValue;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
|
||||
/**
|
||||
@@ -75,6 +76,7 @@ public class ReflectiveMethodExecutor implements MethodExecutor {
|
||||
return this.publicDeclaringClass;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private Class<?> discoverPublicClass(Method method, Class<?> clazz) {
|
||||
if (Modifier.isPublic(clazz.getModifiers())) {
|
||||
try {
|
||||
|
||||
@@ -40,6 +40,7 @@ import org.springframework.expression.PropertyAccessor;
|
||||
import org.springframework.expression.TypedValue;
|
||||
import org.springframework.expression.spel.CodeFlow;
|
||||
import org.springframework.expression.spel.CompilablePropertyAccessor;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
@@ -367,6 +368,7 @@ public class ReflectivePropertyAccessor implements PropertyAccessor {
|
||||
"set", clazz, mustBeStatic, 1, ANY_TYPES);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private Method findMethodForProperty(String[] methodSuffixes, String prefix, Class<?> clazz,
|
||||
boolean mustBeStatic, int numberOfParams, Set<Class<?>> requiredReturnTypes) {
|
||||
|
||||
@@ -427,6 +429,7 @@ public class ReflectivePropertyAccessor implements PropertyAccessor {
|
||||
/**
|
||||
* Find a field of a certain name on a specified class.
|
||||
*/
|
||||
@Nullable
|
||||
protected Field findField(String name, Class<?> clazz, boolean mustBeStatic) {
|
||||
Field[] fields = clazz.getFields();
|
||||
for (Field field : fields) {
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
/**
|
||||
* SpEL's default implementations for various core abstractions.
|
||||
*/
|
||||
@NonNullApi
|
||||
package org.springframework.expression.spel.support;
|
||||
|
||||
import org.springframework.lang.NonNullApi;
|
||||
|
||||
Reference in New Issue
Block a user