|  | 
|  | 1 | +/* | 
|  | 2 | + * Copyright 2002-2025 the original author or 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 | + *      https://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 org.springframework.core; | 
|  | 18 | + | 
|  | 19 | +import java.lang.annotation.Annotation; | 
|  | 20 | +import java.lang.reflect.AnnotatedElement; | 
|  | 21 | +import java.lang.reflect.AnnotatedType; | 
|  | 22 | +import java.lang.reflect.Constructor; | 
|  | 23 | +import java.lang.reflect.Executable; | 
|  | 24 | +import java.lang.reflect.Field; | 
|  | 25 | +import java.lang.reflect.Method; | 
|  | 26 | +import java.lang.reflect.Parameter; | 
|  | 27 | +import java.util.Objects; | 
|  | 28 | +import java.util.function.Predicate; | 
|  | 29 | + | 
|  | 30 | +import kotlin.reflect.KFunction; | 
|  | 31 | +import kotlin.reflect.KParameter; | 
|  | 32 | +import kotlin.reflect.KProperty; | 
|  | 33 | +import kotlin.reflect.jvm.ReflectJvmMapping; | 
|  | 34 | +import org.jspecify.annotations.NonNull; | 
|  | 35 | +import org.jspecify.annotations.NullMarked; | 
|  | 36 | +import org.jspecify.annotations.NullUnmarked; | 
|  | 37 | +import org.jspecify.annotations.Nullable; | 
|  | 38 | + | 
|  | 39 | +/** | 
|  | 40 | + * Constants that indicate the nullness, as well as related utility methods. | 
|  | 41 | + * | 
|  | 42 | + * <p>The nullness applies to a type usage, a field, a method return type or a parameter. | 
|  | 43 | + * <a href="https://jspecify.dev/docs/user-guide/">JSpecify annotations</a> are fully supported, as well as | 
|  | 44 | + * <a href="https://kotlinlang.org/docs/null-safety.html">Kotlin null safety</a> and {@code @Nullable} annotations | 
|  | 45 | + * regardless of their package (from Spring, JSR-305 or Jakarta set of annotations for example). | 
|  | 46 | + * | 
|  | 47 | + * @author Sebastien Deleuze | 
|  | 48 | + * @since 7.0 | 
|  | 49 | + */ | 
|  | 50 | +public enum Nullness { | 
|  | 51 | + | 
|  | 52 | +	/** | 
|  | 53 | +	 * Unspecified nullness (Java and JSpecify {@code @NullUnmarked} defaults). | 
|  | 54 | +	 */ | 
|  | 55 | +	UNSPECIFIED, | 
|  | 56 | + | 
|  | 57 | +	/** | 
|  | 58 | +	 * Can include null (typically specified with a {@code @Nullable} annotation). | 
|  | 59 | +	 */ | 
|  | 60 | +	NULLABLE, | 
|  | 61 | + | 
|  | 62 | +	/** | 
|  | 63 | +	 * Will not include null (Kotlin and JSpecify {@code @NullMarked} defaults). | 
|  | 64 | +	 */ | 
|  | 65 | +	NON_NULL; | 
|  | 66 | + | 
|  | 67 | + | 
|  | 68 | +	/** | 
|  | 69 | +	 * Return the nullness of the given method return type. | 
|  | 70 | +	 * @param method the source for the method return type | 
|  | 71 | +	 * @return the corresponding nullness | 
|  | 72 | +	 */ | 
|  | 73 | +	public static Nullness forMethodReturnType(Method method) { | 
|  | 74 | +		if (KotlinDetector.isKotlinType(method.getDeclaringClass())) { | 
|  | 75 | +			return KotlinDelegate.forMethodReturnType(method); | 
|  | 76 | +		} | 
|  | 77 | +		return (hasNullableAnnotation(method) ? Nullness.NULLABLE : | 
|  | 78 | +				jSpecifyNullness(method, method.getDeclaringClass(), method.getAnnotatedReturnType())); | 
|  | 79 | +	} | 
|  | 80 | + | 
|  | 81 | +	/** | 
|  | 82 | +	 * Return the nullness of the given parameter. | 
|  | 83 | +	 * @param parameter the parameter descriptor | 
|  | 84 | +	 * @return the corresponding nullness | 
|  | 85 | +	 */ | 
|  | 86 | +	public static Nullness forParameter(Parameter parameter) { | 
|  | 87 | +		if (KotlinDetector.isKotlinType(parameter.getDeclaringExecutable().getDeclaringClass())) { | 
|  | 88 | +			// TODO Optimize when kotlin-reflect provide a more direct Parameter to KParameter resolution | 
|  | 89 | +			MethodParameter methodParameter = MethodParameter.forParameter(parameter); | 
|  | 90 | +			return KotlinDelegate.forParameter(methodParameter.getExecutable(), methodParameter.getParameterIndex()); | 
|  | 91 | +		} | 
|  | 92 | +		Executable executable = parameter.getDeclaringExecutable(); | 
|  | 93 | +		return (hasNullableAnnotation(parameter) ? Nullness.NULLABLE : | 
|  | 94 | +				jSpecifyNullness(executable, executable.getDeclaringClass(), parameter.getAnnotatedType())); | 
|  | 95 | +	} | 
|  | 96 | + | 
|  | 97 | +	/** | 
|  | 98 | +	 * Return the nullness of the given method parameter. | 
|  | 99 | +	 * @param methodParameter the method parameter descriptor | 
|  | 100 | +	 * @return the corresponding nullness | 
|  | 101 | +	 */ | 
|  | 102 | +	public static Nullness forMethodParameter(MethodParameter methodParameter) { | 
|  | 103 | +		return (methodParameter.getParameterIndex() < 0 ? | 
|  | 104 | +				forMethodReturnType(Objects.requireNonNull(methodParameter.getMethod())) : | 
|  | 105 | +				forParameter(methodParameter.getParameter())); | 
|  | 106 | +	} | 
|  | 107 | + | 
|  | 108 | +	/** | 
|  | 109 | +	 * Return the nullness of the given field. | 
|  | 110 | +	 * @param field the field descriptor | 
|  | 111 | +	 * @return the corresponding nullness | 
|  | 112 | +	 */ | 
|  | 113 | +	public static Nullness forField(Field field) { | 
|  | 114 | +		if (KotlinDetector.isKotlinType(field.getDeclaringClass())) { | 
|  | 115 | +			return KotlinDelegate.forField(field); | 
|  | 116 | +		} | 
|  | 117 | +		return (hasNullableAnnotation(field) ? Nullness.NULLABLE : | 
|  | 118 | +				jSpecifyNullness(field, field.getDeclaringClass(), field.getAnnotatedType())); | 
|  | 119 | +	} | 
|  | 120 | + | 
|  | 121 | + | 
|  | 122 | +	// Check method and parameter level @Nullable annotations regardless of the package (including Spring and JSR 305 annotations) | 
|  | 123 | +	private static boolean hasNullableAnnotation(AnnotatedElement element) { | 
|  | 124 | +		for (Annotation annotation : element.getDeclaredAnnotations()) { | 
|  | 125 | +			if ("Nullable".equals(annotation.annotationType().getSimpleName())) { | 
|  | 126 | +				return true; | 
|  | 127 | +			} | 
|  | 128 | +		} | 
|  | 129 | +		return false; | 
|  | 130 | +	} | 
|  | 131 | + | 
|  | 132 | +	private static Nullness jSpecifyNullness(AnnotatedElement annotatedElement, Class<?> declaringClass, AnnotatedType annotatedType) { | 
|  | 133 | +		if (annotatedType.isAnnotationPresent(Nullable.class)) { | 
|  | 134 | +			return Nullness.NULLABLE; | 
|  | 135 | +		} | 
|  | 136 | +		if (annotatedType.isAnnotationPresent(NonNull.class)) { | 
|  | 137 | +			return Nullness.NON_NULL; | 
|  | 138 | +		} | 
|  | 139 | +		Nullness nullness = Nullness.UNSPECIFIED; | 
|  | 140 | +		// Package level | 
|  | 141 | +		Package declaringPackage = declaringClass.getPackage(); | 
|  | 142 | +		if (declaringPackage.isAnnotationPresent(NullMarked.class)) { | 
|  | 143 | +			nullness = Nullness.NON_NULL; | 
|  | 144 | +		} | 
|  | 145 | +		// Class level | 
|  | 146 | +		if (declaringClass.isAnnotationPresent(NullMarked.class)) { | 
|  | 147 | +			nullness = Nullness.NON_NULL; | 
|  | 148 | +		} | 
|  | 149 | +		else if (declaringClass.isAnnotationPresent(NullUnmarked.class)) { | 
|  | 150 | +			nullness = Nullness.UNSPECIFIED; | 
|  | 151 | +		} | 
|  | 152 | +		// Annotated element level | 
|  | 153 | +		if (annotatedElement.isAnnotationPresent(NullMarked.class)) { | 
|  | 154 | +			nullness = Nullness.NON_NULL; | 
|  | 155 | +		} | 
|  | 156 | +		else if (annotatedElement.isAnnotationPresent(NullUnmarked.class)) { | 
|  | 157 | +			nullness = Nullness.UNSPECIFIED; | 
|  | 158 | +		} | 
|  | 159 | +		return nullness; | 
|  | 160 | +	} | 
|  | 161 | + | 
|  | 162 | +	/** | 
|  | 163 | +	 * Inner class to avoid a hard dependency on Kotlin at runtime. | 
|  | 164 | +	 */ | 
|  | 165 | +	private static class KotlinDelegate { | 
|  | 166 | + | 
|  | 167 | + | 
|  | 168 | +		public static Nullness forMethodReturnType(Method method) { | 
|  | 169 | +			KFunction<?> function = ReflectJvmMapping.getKotlinFunction(method); | 
|  | 170 | +			if (function != null && function.getReturnType().isMarkedNullable()) { | 
|  | 171 | +				return Nullness.NULLABLE; | 
|  | 172 | +			} | 
|  | 173 | +			return Nullness.NON_NULL; | 
|  | 174 | +		} | 
|  | 175 | + | 
|  | 176 | +		public static Nullness forParameter(Executable executable, int parameterIndex) { | 
|  | 177 | +			KFunction<?> function; | 
|  | 178 | +			Predicate<KParameter> predicate; | 
|  | 179 | +			if (executable instanceof Method method) { | 
|  | 180 | +				function = ReflectJvmMapping.getKotlinFunction(method); | 
|  | 181 | +				predicate = p -> KParameter.Kind.VALUE.equals(p.getKind()); | 
|  | 182 | +			} | 
|  | 183 | +			else { | 
|  | 184 | +				function = ReflectJvmMapping.getKotlinFunction((Constructor<?>) executable); | 
|  | 185 | +				predicate = p -> (KParameter.Kind.VALUE.equals(p.getKind()) || | 
|  | 186 | +						KParameter.Kind.INSTANCE.equals(p.getKind())); | 
|  | 187 | +			} | 
|  | 188 | +			if (function == null) { | 
|  | 189 | +				return Nullness.UNSPECIFIED; | 
|  | 190 | +			} | 
|  | 191 | +			int i = 0; | 
|  | 192 | +			for (KParameter kParameter : function.getParameters()) { | 
|  | 193 | +				if (predicate.test(kParameter) && parameterIndex == i++) { | 
|  | 194 | +					return (kParameter.getType().isMarkedNullable() ? Nullness.NULLABLE : Nullness.NON_NULL); | 
|  | 195 | +				} | 
|  | 196 | +			} | 
|  | 197 | +			return Nullness.UNSPECIFIED; | 
|  | 198 | +		} | 
|  | 199 | + | 
|  | 200 | +		public static Nullness forField(Field field) { | 
|  | 201 | +			KProperty<?> property = ReflectJvmMapping.getKotlinProperty(field); | 
|  | 202 | +			if (property != null && property.getReturnType().isMarkedNullable()) { | 
|  | 203 | +				return Nullness.NULLABLE; | 
|  | 204 | +			} | 
|  | 205 | +			return Nullness.NON_NULL; | 
|  | 206 | +		} | 
|  | 207 | + | 
|  | 208 | +	} | 
|  | 209 | + | 
|  | 210 | +} | 
0 commit comments