Skip to content

Commit 1bf5810

Browse files
anthologiaartembilan
authored andcommitted
GH-10083: Apply Nullability to core util package
Related to: #10083 * Add null checks and `@Nullable` where appropriate * Handle optional `ErrorHandler` in `PartitionedDispatcher` * Use `Objects.requireNonNull()` for reflection method lookups * Add `Assert.notNull()` for UUID conversion * Add `@Contract` to `UUIDConverter` methods * Replace `Objects.requireNonNull` with ternary operator in `ClassUtils` * Some minor code clean up Signed-off-by: Jooyoung Pyoung <[email protected]>
1 parent 8fdee30 commit 1bf5810

File tree

12 files changed

+90
-41
lines changed

12 files changed

+90
-41
lines changed

spring-integration-core/src/main/java/org/springframework/integration/channel/ExecutorChannel.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,8 @@ public final void onInit() {
115115
Assert.state(getDispatcher().getHandlerCount() == 0, "You cannot subscribe() until the channel "
116116
+ "bean is fully initialized by the framework. Do not subscribe in a @Bean definition");
117117
super.onInit();
118+
119+
Assert.state(this.executor != null, "Executor must be configured");
118120
if (!(this.executor instanceof ErrorHandlingTaskExecutor)) {
119121
ErrorHandler errorHandler = ChannelUtils.getErrorHandler(getBeanFactory());
120122
this.executor = new ErrorHandlingTaskExecutor(this.executor, errorHandler);

spring-integration-core/src/main/java/org/springframework/integration/dispatcher/PartitionedDispatcher.java

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -188,8 +188,11 @@ private void populatedPartitions() {
188188
private UnicastingDispatcher newPartition() {
189189
ExecutorService executor = Executors.newSingleThreadExecutor(this.threadFactory);
190190
this.executors.add(executor);
191-
DelegateDispatcher delegateDispatcher =
192-
new DelegateDispatcher(new ErrorHandlingTaskExecutor(executor, this.errorHandler));
191+
192+
Executor effectiveExecutor = this.errorHandler != null
193+
? new ErrorHandlingTaskExecutor(executor, this.errorHandler)
194+
: executor;
195+
DelegateDispatcher delegateDispatcher = new DelegateDispatcher(effectiveExecutor);
193196
delegateDispatcher.setFailoverStrategy(this.failoverStrategy);
194197
delegateDispatcher.setLoadBalancingStrategy(this.loadBalancingStrategy);
195198
delegateDispatcher.setMessageHandlingTaskDecorator(this.messageHandlingTaskDecorator);

spring-integration-core/src/main/java/org/springframework/integration/util/AbstractExpressionEvaluator.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,8 +54,10 @@ public abstract class AbstractExpressionEvaluator implements BeanFactoryAware, I
5454

5555
private boolean simpleEvaluationContext;
5656

57+
@SuppressWarnings("NullAway.Init")
5758
private volatile EvaluationContext evaluationContext;
5859

60+
@SuppressWarnings("NullAway.Init")
5961
private volatile BeanFactory beanFactory;
6062

6163
private volatile MessageBuilderFactory messageBuilderFactory = new DefaultMessageBuilderFactory();

spring-integration-core/src/main/java/org/springframework/integration/util/BeanFactoryTypeConverter.java

Lines changed: 25 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@
4040
* @author Soby Chacko
4141
* @author Artem Bilan
4242
* @author Ngoc Nhan
43+
* @author Jooyoung Pyoung
4344
*/
4445
public class BeanFactoryTypeConverter implements TypeConverter, BeanFactoryAware {
4546

@@ -86,7 +87,11 @@ public boolean canConvert(Class<?> sourceType, Class<?> targetType) {
8687
}
8788

8889
@Override
89-
public boolean canConvert(TypeDescriptor sourceTypeDescriptor, TypeDescriptor targetTypeDescriptor) {
90+
public boolean canConvert(@Nullable TypeDescriptor sourceTypeDescriptor, TypeDescriptor targetTypeDescriptor) {
91+
if (sourceTypeDescriptor == null) {
92+
return true;
93+
}
94+
9095
if (this.conversionService.canConvert(sourceTypeDescriptor, targetTypeDescriptor)) {
9196
return true;
9297
}
@@ -97,15 +102,17 @@ public boolean canConvert(TypeDescriptor sourceTypeDescriptor, TypeDescriptor ta
97102
}
98103

99104
@Override // NOSONAR
100-
public Object convertValue(Object value, TypeDescriptor sourceType, TypeDescriptor targetType) {
105+
public @Nullable Object convertValue(@Nullable Object value, @Nullable TypeDescriptor sourceType,
106+
TypeDescriptor targetType) {
107+
101108
// Echoes org.springframework.expression.common.ExpressionUtils.convertTypedValue()
102109
if ((targetType.getType() == Void.class || targetType.getType() == Void.TYPE) && value == null) {
103110
return null;
104111
}
105112
/*
106113
* INT-2630 Spring 3.1 now converts ALL arguments; we know we don't need to convert MessageHeaders
107114
* or MessageHistory; the MapToMap converter requires a no-arg constructor.
108-
* Also INT-2650 - don't convert large byte[]
115+
* Also, INT-2650 - don't convert large byte[]
109116
*/
110117
if (sourceType != null) {
111118
Class<?> sourceClass = sourceType.getType();
@@ -120,19 +127,19 @@ public Object convertValue(Object value, TypeDescriptor sourceType, TypeDescript
120127
return this.conversionService.convert(value, sourceType, targetType);
121128
}
122129

123-
Object editorResult = valueFromEditorIfAny(value, sourceType.getType(), targetType);
124-
125-
if (editorResult == null) {
126-
synchronized (this.delegate) {
127-
return this.delegate.convertIfNecessary(value, targetType.getType());
130+
if (sourceType != null) {
131+
Object editorResult = valueFromEditorIfAny(value, sourceType.getType(), targetType);
132+
if (editorResult != null) {
133+
return editorResult;
128134
}
129135
}
130-
else {
131-
return editorResult;
136+
137+
synchronized (this.delegate) {
138+
return this.delegate.convertIfNecessary(value, targetType.getType());
132139
}
133140
}
134141

135-
private PropertyEditor getDefaultEditor(Class<?> sourceType) {
142+
private @Nullable PropertyEditor getDefaultEditor(Class<?> sourceType) {
136143
PropertyEditor defaultEditor;
137144
if (this.haveCalledDelegateGetDefaultEditor) {
138145
defaultEditor = this.delegate.getDefaultEditor(sourceType);
@@ -147,8 +154,13 @@ private PropertyEditor getDefaultEditor(Class<?> sourceType) {
147154
return defaultEditor;
148155
}
149156

150-
@Nullable
151-
private Object valueFromEditorIfAny(Object value, Class<?> sourceClass, TypeDescriptor targetType) {
157+
private @Nullable Object valueFromEditorIfAny(@Nullable Object value, @Nullable Class<?> sourceClass,
158+
TypeDescriptor targetType) {
159+
160+
if (value == null || sourceClass == null) {
161+
return null;
162+
}
163+
152164
if (!String.class.isAssignableFrom(sourceClass)) {
153165
PropertyEditor editor = this.delegate.findCustomEditor(sourceClass, null);
154166
if (editor == null) {

spring-integration-core/src/main/java/org/springframework/integration/util/ClassUtils.java

Lines changed: 20 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import java.lang.reflect.Proxy;
2121
import java.util.HashMap;
2222
import java.util.Map;
23+
import java.util.Objects;
2324
import java.util.Set;
2425
import java.util.function.Function;
2526
import java.util.function.Supplier;
@@ -46,12 +47,14 @@ public abstract class ClassUtils {
4647
/**
4748
* The {@link Function#apply(Object)} method object.
4849
*/
50+
@SuppressWarnings("NullAway") // Reflection
4951
public static final Method FUNCTION_APPLY_METHOD =
5052
ReflectionUtils.findMethod(Function.class, "apply", (Class<?>[]) null);
5153

5254
/**
5355
* The {@link Supplier#get()} method object.
5456
*/
57+
@SuppressWarnings("NullAway") // Reflection
5558
public static final Method SUPPLIER_GET_METHOD =
5659
ReflectionUtils.findMethod(Supplier.class, "get", (Class<?>[]) null);
5760

@@ -73,17 +76,17 @@ public abstract class ClassUtils {
7376
/**
7477
* The {@code kotlin.jvm.functions.Function0} class object.
7578
*/
76-
public static final Class<?> KOTLIN_FUNCTION_0_CLASS;
79+
public static final @Nullable Class<?> KOTLIN_FUNCTION_0_CLASS;
7780

7881
/**
7982
* The {@code kotlin.jvm.functions.Function0#invoke} method object.
8083
*/
81-
public static final Method KOTLIN_FUNCTION_0_INVOKE_METHOD;
84+
public static final @Nullable Method KOTLIN_FUNCTION_0_INVOKE_METHOD;
8285

8386
/**
8487
* The {@code kotlin.jvm.functions.Function1} class object.
8588
*/
86-
public static final Class<?> KOTLIN_FUNCTION_1_CLASS;
89+
public static final @Nullable Class<?> KOTLIN_FUNCTION_1_CLASS;
8790

8891
static {
8992
PRIMITIVE_WRAPPER_TYPE_MAP.put(Boolean.class, boolean.class);
@@ -107,7 +110,8 @@ public abstract class ClassUtils {
107110
ReflectionUtils.rethrowRuntimeException(e);
108111
}
109112

110-
SELECTOR_ACCEPT_METHOD = ReflectionUtils.findMethod(genericSelectorClass, "accept", (Class<?>[]) null);
113+
SELECTOR_ACCEPT_METHOD =
114+
Objects.requireNonNull(ReflectionUtils.findMethod(genericSelectorClass, "accept", (Class<?>[]) null));
111115

112116
Class<?> genericTransformerClass = null;
113117
try {
@@ -120,7 +124,8 @@ public abstract class ClassUtils {
120124
}
121125

122126
TRANSFORMER_TRANSFORM_METHOD =
123-
ReflectionUtils.findMethod(genericTransformerClass, "transform", (Class<?>[]) null);
127+
Objects.requireNonNull(
128+
ReflectionUtils.findMethod(genericTransformerClass, "transform", (Class<?>[]) null));
124129

125130
Class<?> genericHandlerClass = null;
126131
try {
@@ -132,7 +137,8 @@ public abstract class ClassUtils {
132137
ReflectionUtils.rethrowRuntimeException(e);
133138
}
134139

135-
HANDLER_HANDLE_METHOD = ReflectionUtils.findMethod(genericHandlerClass, "handle", (Class<?>[]) null);
140+
HANDLER_HANDLE_METHOD =
141+
Objects.requireNonNull(ReflectionUtils.findMethod(genericHandlerClass, "handle", (Class<?>[]) null));
136142

137143
if (KotlinDetector.isKotlinPresent()) {
138144
Class<?> kotlinClass = null;
@@ -170,7 +176,8 @@ public abstract class ClassUtils {
170176
}
171177
}
172178

173-
public static Class<?> findClosestMatch(Class<?> type, Set<Class<?>> candidates, boolean failOnTie) {
179+
@SuppressWarnings("NullAway")
180+
public static @Nullable Class<?> findClosestMatch(Class<?> type, Set<Class<?>> candidates, boolean failOnTie) {
174181
int minTypeDiffWeight = Integer.MAX_VALUE;
175182
Class<?> closestMatch = null;
176183
for (Class<?> candidate : candidates) {
@@ -181,8 +188,11 @@ public static Class<?> findClosestMatch(Class<?> type, Set<Class<?>> candidates,
181188
}
182189
else if (failOnTie && typeDiffWeight < Integer.MAX_VALUE && (typeDiffWeight == minTypeDiffWeight)) {
183190
throw new IllegalStateException("Unresolvable ambiguity while attempting to find closest match for [" +
184-
type.getName() + "]. Candidate types [" + closestMatch.getName() + "] and [" +
185-
candidate.getName() + "] have equal weight.");
191+
type.getName() + "]. Candidate types [" +
192+
closestMatch.getName() +
193+
"] and [" +
194+
candidate.getName() +
195+
"] have equal weight.");
186196
}
187197
}
188198
return closestMatch;
@@ -219,8 +229,7 @@ else if (org.springframework.util.ClassUtils.isAssignable(candidate, superClass)
219229
* @param clazz the wrapper class to check
220230
* @return the corresponding primitive if the clazz is a wrapper, otherwise null
221231
*/
222-
@Nullable
223-
public static Class<?> resolvePrimitiveType(Class<?> clazz) {
232+
public static @Nullable Class<?> resolvePrimitiveType(Class<?> clazz) {
224233
return PRIMITIVE_WRAPPER_TYPE_MAP.get(clazz);
225234
}
226235

spring-integration-core/src/main/java/org/springframework/integration/util/CompoundTrigger.java

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@
1818

1919
import java.time.Instant;
2020

21+
import org.jspecify.annotations.Nullable;
22+
2123
import org.springframework.scheduling.Trigger;
2224
import org.springframework.scheduling.TriggerContext;
2325
import org.springframework.util.Assert;
@@ -38,7 +40,7 @@ public class CompoundTrigger implements Trigger {
3840

3941
private volatile Trigger primary;
4042

41-
private volatile Trigger override;
43+
private volatile @Nullable Trigger override;
4244

4345
/**
4446
* Construct a compound trigger with the supplied primary trigger.
@@ -62,12 +64,12 @@ public final void setPrimary(Trigger primary) {
6264
* primary trigger.
6365
* @param override the override trigger, or null.
6466
*/
65-
public void setOverride(Trigger override) {
67+
public void setOverride(@Nullable Trigger override) {
6668
this.override = override;
6769
}
6870

6971
@Override
70-
public Instant nextExecution(TriggerContext triggerContext) {
72+
public @Nullable Instant nextExecution(TriggerContext triggerContext) {
7173
if (this.override != null) {
7274
return this.override.nextExecution(triggerContext);
7375
}

spring-integration-core/src/main/java/org/springframework/integration/util/FunctionIterator.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@
3636
*/
3737
public class FunctionIterator<T, V> implements CloseableIterator<V> {
3838

39-
private final AutoCloseable closeable;
39+
private final @Nullable AutoCloseable closeable;
4040

4141
private final Iterator<T> iterator;
4242

spring-integration-core/src/main/java/org/springframework/integration/util/IntegrationReactiveUtils.java

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ public final class IntegrationReactiveUtils {
7474
public static final boolean isContextPropagationPresent = ClassUtils.isPresent(
7575
"io.micrometer.context.ContextSnapshot", IntegrationReactiveUtils.class.getClassLoader());
7676

77-
private static final ContextSnapshotFactory CONTEXT_SNAPSHOT_FACTORY =
77+
private static final @Nullable ContextSnapshotFactory CONTEXT_SNAPSHOT_FACTORY =
7878
isContextPropagationPresent ? ContextSnapshotFactory.builder().build() : null;
7979

8080
private IntegrationReactiveUtils() {
@@ -89,6 +89,10 @@ private IntegrationReactiveUtils() {
8989
* @since 6.2.5
9090
*/
9191
public static ContextView captureReactorContext() {
92+
if (CONTEXT_SNAPSHOT_FACTORY == null) {
93+
return Context.empty();
94+
}
95+
9296
return isContextPropagationPresent
9397
? CONTEXT_SNAPSHOT_FACTORY.captureAll().updateContext(Context.empty())
9498
: Context.empty();
@@ -103,8 +107,10 @@ public static ContextView captureReactorContext() {
103107
* Or null if there is no {@code io.micrometer:context-propagation} library is on classpath.
104108
* @since 6.2.5
105109
*/
106-
@Nullable
107-
public static AutoCloseable setThreadLocalsFromReactorContext(ContextView context) {
110+
public static @Nullable AutoCloseable setThreadLocalsFromReactorContext(ContextView context) {
111+
if (CONTEXT_SNAPSHOT_FACTORY == null) {
112+
return null;
113+
}
108114
return isContextPropagationPresent ? CONTEXT_SNAPSHOT_FACTORY.setThreadLocalsFrom(context) : null;
109115
}
110116

spring-integration-core/src/main/java/org/springframework/integration/util/MessagingAnnotationUtils.java

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
import org.springframework.core.annotation.MergedAnnotations;
3535
import org.springframework.integration.annotation.EndpointId;
3636
import org.springframework.integration.annotation.Payloads;
37+
import org.springframework.lang.Contract;
3738
import org.springframework.messaging.MessagingException;
3839
import org.springframework.messaging.handler.annotation.Header;
3940
import org.springframework.messaging.handler.annotation.Headers;
@@ -67,7 +68,7 @@ public final class MessagingAnnotationUtils {
6768
* @return The value.
6869
*/
6970
@SuppressWarnings("unchecked")
70-
public static <T> T resolveAttribute(List<Annotation> annotations, String name, Class<T> requiredType) {
71+
public static @Nullable <T> T resolveAttribute(List<Annotation> annotations, String name, Class<T> requiredType) {
7172
for (Annotation annotation : annotations) {
7273
Object value = AnnotationUtils.getValue(annotation, name);
7374
if (requiredType.isInstance(value) && hasValue(value)) {
@@ -83,7 +84,7 @@ public static <T> T resolveAttribute(List<Annotation> annotations, String name,
8384
* @return {@code false} when {@code annotationValue} is null, an empty string, an empty array, or any annotation
8485
* whose 'value' field is set to {@link ValueConstants#DEFAULT_NONE} - {@code true} otherwise
8586
*/
86-
public static boolean hasValue(Object annotationValue) {
87+
public static boolean hasValue(@Nullable Object annotationValue) {
8788
if (annotationValue == null) {
8889
return false;
8990
}
@@ -100,7 +101,9 @@ public static boolean hasValue(Object annotationValue) {
100101
!ValueConstants.DEFAULT_NONE.equals(AnnotationUtils.getValue((Annotation) annotationValue));
101102
}
102103

103-
public static Method findAnnotatedMethod(Object target, final Class<? extends Annotation> annotationType) {
104+
public static @Nullable Method findAnnotatedMethod(Object target,
105+
final Class<? extends Annotation> annotationType) {
106+
104107
final AtomicReference<Method> reference = new AtomicReference<>();
105108

106109
ReflectionUtils.doWithMethods(AopProxyUtils.ultimateTargetClass(target),
@@ -120,7 +123,8 @@ public static Method findAnnotatedMethod(Object target, final Class<? extends An
120123
* @throws MessagingException if more than one of {@link Payload}, {@link Header}
121124
* or {@link Headers} annotations are presented.
122125
*/
123-
public static Annotation findMessagePartAnnotation(Annotation[] annotations, boolean payloads) {
126+
@Contract("null, _ -> null")
127+
public static @Nullable Annotation findMessagePartAnnotation(Annotation @Nullable [] annotations, boolean payloads) {
124128
if (annotations == null || annotations.length == 0) {
125129
return null;
126130
}

spring-integration-core/src/main/java/org/springframework/integration/util/UUIDConverter.java

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,10 @@
2323
import java.util.UUID;
2424
import java.util.regex.Pattern;
2525

26+
import org.jspecify.annotations.Nullable;
27+
2628
import org.springframework.core.convert.converter.Converter;
29+
import org.springframework.lang.Contract;
2730
import org.springframework.util.ClassUtils;
2831

2932
/**
@@ -45,7 +48,8 @@ public class UUIDConverter implements Converter<Object, UUID> {
4548
* @see org.springframework.core.convert.converter.Converter#convert(java.lang.Object)
4649
*/
4750
@Override
48-
public UUID convert(Object source) {
51+
@Contract("null -> null; !null -> !null")
52+
public @Nullable UUID convert(@Nullable Object source) {
4953
return getUUID(source);
5054
}
5155

@@ -63,7 +67,8 @@ public UUID convert(Object source) {
6367
* @param input an Object
6468
* @return a UUID constructed from the input
6569
*/
66-
public static UUID getUUID(Object input) {
70+
@Contract("null -> null; !null -> !null")
71+
public static @Nullable UUID getUUID(@Nullable Object input) {
6772
if (input == null) {
6873
return null;
6974
}
@@ -89,7 +94,8 @@ private static UUID fromStringBytes(String input) {
8994
return UUID.nameUUIDFromBytes(input.getBytes(StandardCharsets.UTF_8));
9095
}
9196

92-
private static byte[] serialize(Object object) {
97+
@Contract("null -> null; !null -> !null")
98+
private static byte @Nullable [] serialize(@Nullable Object object) {
9399
if (object == null) {
94100
return null;
95101
}

0 commit comments

Comments
 (0)