1 /*
2  * Copyright 2017-2020 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 package org.springframework.data.util;
17
18 import lombok.experimental.UtilityClass;
19
20 import java.lang.annotation.Annotation;
21 import java.lang.annotation.ElementType;
22 import java.lang.reflect.AnnotatedElement;
23 import java.lang.reflect.Method;
24 import java.util.Arrays;
25 import java.util.Collections;
26 import java.util.HashSet;
27 import java.util.List;
28 import java.util.Map;
29 import java.util.Optional;
30 import java.util.Set;
31 import java.util.function.Predicate;
32 import java.util.stream.Collectors;
33
34 import javax.annotation.Nonnull;
35
36 import org.springframework.core.MethodParameter;
37 import org.springframework.core.annotation.AnnotatedElementUtils;
38 import org.springframework.core.annotation.AnnotationUtils;
39 import org.springframework.lang.NonNullApi;
40 import org.springframework.lang.Nullable;
41 import org.springframework.util.ClassUtils;
42 import org.springframework.util.MultiValueMap;
43
44 /**
45  * Utility methods to introspect nullability rules declared in packages, classes and methods.
46  * <p/>
47  * Nullability rules are declared using {@link NonNullApi} and {@link Nullable} and JSR-305
48  * {@link javax.annotation.Nonnull} annotations. By default (no annotation use), a package and its types are considered
49  * allowing {@literal null} values in return values and method parameters. Nullability rules are expressed by annotating
50  * a package with a JSR-305 meta annotation such as Spring's {@link NonNullApi}. All types of the package inherit the
51  * package rule. Subpackages do not inherit nullability rules and must be annotated themself.
52  *
53  * <pre class="code">
54  * &#64;org.springframework.lang.NonNullApi
55  * package com.example;
56  * </pre>
57  *
58  * {@link Nullable} selectively permits {@literal null} values for method return values or method parameters by
59  * annotating the method respectively the parameters:
60  *
61  * <pre class="code">
62  * public class ExampleClass {
63  *
64  *     String shouldNotReturnNull(@Nullable String acceptsNull, String doesNotAcceptNull) {
65  *         // …
66  *     }
67  *
68  *     &#64;Nullable
69  *     String nullableReturn(String parameter) {
70  *         // …
71  *     }
72  * }
73  * </pre>
74  * <p/>
75  * {@link javax.annotation.Nonnull} is suitable for composition of meta-annotations and expresses via
76  * {@link Nonnull#when()} in which cases non-nullability is applicable.
77  *
78  * @author Mark Paluch
79  * @since 2.0
80  * @see NonNullApi
81  * @see Nullable
82  * @see Nonnull
83  */

84 @UtilityClass
85 public class NullableUtils {
86
87     private static final String NON_NULL_CLASS_NAME = "javax.annotation.Nonnull";
88     private static final String TYPE_QUALIFIER_CLASS_NAME = "javax.annotation.meta.TypeQualifierDefault";
89
90     private static final Optional<Class<Annotation>> NON_NULL_ANNOTATION_CLASS = findClass(NON_NULL_CLASS_NAME);
91
92     private static final Set<Class<?>> NULLABLE_ANNOTATIONS = findClasses(Nullable.class.getName());
93     private static final Set<Class<?>> NON_NULLABLE_ANNOTATIONS = findClasses("reactor.util.lang.NonNullApi",
94             NonNullApi.class.getName());
95
96     private static final Set<String> WHEN_NULLABLE = new HashSet<>(Arrays.asList("UNKNOWN""MAYBE""NEVER"));
97     private static final Set<String> WHEN_NON_NULLABLE = new HashSet<>(Collections.singletonList("ALWAYS"));
98
99     /**
100      * Determine whether {@link ElementType} in the scope of {@link Method} requires non-{@literal null} values.
101      * Non-nullability rules are discovered from class and package annotations. Non-null is applied when
102      * {@link javax.annotation.Nonnull} is set to {@link javax.annotation.meta.When#ALWAYS}.
103      *
104      * @param type the class to inspect.
105      * @param elementType the element type.
106      * @return {@literal trueif {@link ElementType} allows {@literal null} values by default.
107      * @see #isNonNull(Annotation, ElementType)
108      */

109     public boolean isNonNull(Method method, ElementType elementType) {
110         return isNonNull(method.getDeclaringClass(), elementType) || isNonNull((AnnotatedElement) method, elementType);
111     }
112
113     /**
114      * Determine whether {@link ElementType} in the scope of {@code type} requires non-{@literal null} values.
115      * Non-nullability rules are discovered from class and package annotations. Non-null is applied when
116      * {@link javax.annotation.Nonnull} is set to {@link javax.annotation.meta.When#ALWAYS}.
117      *
118      * @param type the class to inspect.
119      * @param elementType the element type.
120      * @return {@literal trueif {@link ElementType} allows {@literal null} values by default.
121      * @see #isNonNull(Annotation, ElementType)
122      */

123     public boolean isNonNull(Class<?> type, ElementType elementType) {
124         return isNonNull(type.getPackage(), elementType) || isNonNull((AnnotatedElement) type, elementType);
125     }
126
127     /**
128      * Determine whether {@link ElementType} in the scope of {@link AnnotatedElement} requires non-{@literal null} values.
129      * This method determines default {@link javax.annotation.Nonnull nullability} rules from the annotated element
130      *
131      * @param element the scope of declaration, may be a {@link Package}, {@link Class}, or
132      *          {@link java.lang.reflect.Method}.
133      * @param elementType the element type.
134      * @return {@literal trueif {@link ElementType} allows {@literal null} values by default.
135      */

136     public boolean isNonNull(AnnotatedElement element, ElementType elementType) {
137
138         for (Annotation annotation : element.getAnnotations()) {
139
140             boolean isNonNull = NON_NULL_ANNOTATION_CLASS.isPresent() ? isNonNull(annotation, elementType)
141                     : NON_NULLABLE_ANNOTATIONS.contains(annotation.annotationType());
142
143             if (isNonNull) {
144                 return true;
145             }
146         }
147
148         return false;
149     }
150
151     private static boolean isNonNull(Annotation annotation, ElementType elementType) {
152
153         if (!NON_NULL_ANNOTATION_CLASS.isPresent()) {
154             return false;
155         }
156
157         Class<Annotation> annotationClass = NON_NULL_ANNOTATION_CLASS.get();
158
159         if (annotation.annotationType().equals(annotationClass)) {
160             return true;
161         }
162
163         if (!AnnotationUtils.isAnnotationMetaPresent(annotation.annotationType(), annotationClass)
164                 || !isNonNull(annotation)) {
165             return false;
166         }
167
168         return test(annotation, TYPE_QUALIFIER_CLASS_NAME, "value",
169                 (ElementType[] o) -> Arrays.binarySearch(o, elementType) >= 0);
170     }
171
172     /**
173      * Determine whether a {@link MethodParameter} is explicitly annotated to be considered nullable. Nullability rules
174      * are discovered from method and parameter annotations. A {@link MethodParameter} is considered nullable when
175      * {@link javax.annotation.Nonnull} is set to one of {@link javax.annotation.meta.When#UNKNOWN},
176      * {@link javax.annotation.meta.When#NEVER}, or {@link javax.annotation.meta.When#MAYBE}.
177      *
178      * @param methodParameter the method parameter to inspect.
179      * @return {@literal trueif the parameter is nullable, {@literal false} otherwise.
180      */

181     public static boolean isExplicitNullable(MethodParameter methodParameter) {
182
183         if (methodParameter.getParameterIndex() == -1) {
184             return isExplicitNullable(methodParameter.getMethodAnnotations());
185         }
186
187         return isExplicitNullable(methodParameter.getParameterAnnotations());
188     }
189
190     private static boolean isExplicitNullable(Annotation[] annotations) {
191
192         for (Annotation annotation : annotations) {
193
194             boolean isNullable = NON_NULL_ANNOTATION_CLASS.isPresent() ? isNullable(annotation)
195                     : NULLABLE_ANNOTATIONS.contains(annotation.annotationType());
196
197             if (isNullable) {
198                 return true;
199             }
200         }
201
202         return false;
203     }
204
205     /**
206      * Introspect {@link Annotation} for being either a meta-annotation composed from {@link Nonnull} or {@link Nonnull}
207      * itself expressing non-nullability.
208      *
209      * @param annotation
210      * @return {@literal trueif the annotation expresses non-nullability.
211      */

212     private static boolean isNonNull(Annotation annotation) {
213         return test(annotation, NON_NULL_CLASS_NAME, "when", o -> WHEN_NON_NULLABLE.contains(o.toString()));
214     }
215
216     /**
217      * Introspect {@link Annotation} for being either a meta-annotation composed from {@link Nonnull} or {@link Nonnull}
218      * itself expressing nullability.
219      *
220      * @param annotation
221      * @return {@literal trueif the annotation expresses nullability.
222      */

223     private static boolean isNullable(Annotation annotation) {
224         return test(annotation, NON_NULL_CLASS_NAME, "when", o -> WHEN_NULLABLE.contains(o.toString()));
225     }
226
227     @SuppressWarnings("unchecked")
228     private static <T> boolean test(Annotation annotation, String metaAnnotationName, String attribute,
229             Predicate<T> filter) {
230
231         if (annotation.annotationType().getName().equals(metaAnnotationName)) {
232
233             Map<String, Object> attributes = AnnotationUtils.getAnnotationAttributes(annotation);
234
235             return !attributes.isEmpty() && filter.test((T) attributes.get(attribute));
236         }
237
238         MultiValueMap<String, Object> attributes = AnnotatedElementUtils
239                 .getAllAnnotationAttributes(annotation.annotationType(), metaAnnotationName);
240
241         if (attributes == null || attributes.isEmpty()) {
242             return false;
243         }
244
245         List<Object> elementTypes = attributes.get(attribute);
246
247         for (Object value : elementTypes) {
248
249             if (filter.test((T) value)) {
250                 return true;
251             }
252         }
253         return false;
254     }
255
256     private static Set<Class<?>> findClasses(String... classNames) {
257
258         return Arrays.stream(classNames) //
259                 .map(NullableUtils::findClass) //
260                 .filter(Optional::isPresent) //
261                 .map(Optional::get) //
262                 .collect(Collectors.toSet());
263     }
264
265     @SuppressWarnings({ "unchecked""rawtypes" })
266     private static <T> Optional<Class<T>> findClass(String className) {
267
268         try {
269             return Optional.of((Class) ClassUtils.forName(className, NullableUtils.class.getClassLoader()));
270         } catch (ClassNotFoundException e) {
271             return Optional.empty();
272         }
273     }
274 }
275