diff --git a/spring-core/src/main/java/org/springframework/core/ReactiveTypeDescriptor.java b/spring-core/src/main/java/org/springframework/core/ReactiveTypeDescriptor.java index d056613e947a..433e2bf3b715 100644 --- a/spring-core/src/main/java/org/springframework/core/ReactiveTypeDescriptor.java +++ b/spring-core/src/main/java/org/springframework/core/ReactiveTypeDescriptor.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2023 the original author or authors. + * Copyright 2002-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -98,7 +98,9 @@ public boolean supportsEmpty() { */ public Object getEmptyValue() { Assert.state(this.emptySupplier != null, "Empty values not supported"); - return this.emptySupplier.get(); + Object emptyValue = this.emptySupplier.get(); + Assert.notNull(emptyValue, "Invalid null return value from emptySupplier"); + return emptyValue; } /** diff --git a/spring-core/src/main/java/org/springframework/core/ResolvableType.java b/spring-core/src/main/java/org/springframework/core/ResolvableType.java index e02a2451d172..46acaf778919 100644 --- a/spring-core/src/main/java/org/springframework/core/ResolvableType.java +++ b/spring-core/src/main/java/org/springframework/core/ResolvableType.java @@ -1118,22 +1118,21 @@ public static ResolvableType forClassWithGenerics(Class clazz, Class... ge * @return a {@code ResolvableType} for the specific class and generics * @see #forClassWithGenerics(Class, Class...) */ - public static ResolvableType forClassWithGenerics(Class clazz, ResolvableType... generics) { + public static ResolvableType forClassWithGenerics(Class clazz, @Nullable ResolvableType... generics) { Assert.notNull(clazz, "Class must not be null"); - Assert.notNull(generics, "Generics array must not be null"); TypeVariable[] variables = clazz.getTypeParameters(); - Assert.isTrue(variables.length == generics.length, - () -> "Mismatched number of generics specified for " + clazz.toGenericString()); - - Type[] arguments = new Type[generics.length]; - for (int i = 0; i < generics.length; i++) { - ResolvableType generic = generics[i]; + if (generics != null) { + Assert.isTrue(variables.length == generics.length, + () -> "Mismatched number of generics specified for " + clazz.toGenericString()); + } + Type[] arguments = new Type[variables.length]; + for (int i = 0; i < variables.length; i++) { + ResolvableType generic = (generics != null ? generics[i] : null); Type argument = (generic != null ? generic.getType() : null); arguments[i] = (argument != null && !(argument instanceof TypeVariable) ? argument : variables[i]); } - - ParameterizedType syntheticType = new SyntheticParameterizedType(clazz, arguments); - return forType(syntheticType, new TypeVariablesVariableResolver(variables, generics)); + return forType(new SyntheticParameterizedType(clazz, arguments), + (generics != null ? new TypeVariablesVariableResolver(variables, generics) : null)); } /** @@ -1388,7 +1387,7 @@ static ResolvableType forMethodParameter( */ public static ResolvableType forArrayComponent(ResolvableType componentType) { Assert.notNull(componentType, "Component type must not be null"); - Class arrayType = componentType.resolve().arrayType(); + Class arrayType = componentType.toClass().arrayType(); return new ResolvableType(arrayType, componentType, null, null); } diff --git a/spring-core/src/main/java/org/springframework/core/annotation/AnnotatedMethod.java b/spring-core/src/main/java/org/springframework/core/annotation/AnnotatedMethod.java index 12be91e81786..e50e79bf9004 100644 --- a/spring-core/src/main/java/org/springframework/core/annotation/AnnotatedMethod.java +++ b/spring-core/src/main/java/org/springframework/core/annotation/AnnotatedMethod.java @@ -278,6 +278,7 @@ public Class getContainingClass() { } @Override + @Nullable public T getMethodAnnotation(Class annotationType) { return AnnotatedMethod.this.getMethodAnnotation(annotationType); } diff --git a/spring-core/src/main/java/org/springframework/core/annotation/AnnotationAttributes.java b/spring-core/src/main/java/org/springframework/core/annotation/AnnotationAttributes.java index dfe98c12382b..7732e008062e 100644 --- a/spring-core/src/main/java/org/springframework/core/annotation/AnnotationAttributes.java +++ b/spring-core/src/main/java/org/springframework/core/annotation/AnnotationAttributes.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2023 the original author or authors. + * Copyright 2002-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -114,19 +114,6 @@ public AnnotationAttributes(Class annotationType) { this(annotationType, false); } - /** - * Create a new, empty {@link AnnotationAttributes} instance for the - * specified {@code annotationType}. - * @param annotationType the annotation type name represented by this - * {@code AnnotationAttributes} instance; never {@code null} - * @param classLoader the ClassLoader to try to load the annotation type on, - * or {@code null} to just store the annotation type name - * @since 4.3.2 - */ - public AnnotationAttributes(String annotationType, @Nullable ClassLoader classLoader) { - this(getAnnotationType(annotationType, classLoader), false); - } - /** * Create a possibly already validated new, empty * {@link AnnotationAttributes} instance for the specified @@ -143,6 +130,21 @@ public AnnotationAttributes(String annotationType, @Nullable ClassLoader classLo this.validated = validated; } + /** + * Create a new, empty {@link AnnotationAttributes} instance for the + * specified {@code annotationType}. + * @param annotationType the annotation type name represented by this + * {@code AnnotationAttributes} instance; never {@code null} + * @param classLoader the ClassLoader to try to load the annotation type on, + * or {@code null} to just store the annotation type name + * @since 4.3.2 + */ + public AnnotationAttributes(String annotationType, @Nullable ClassLoader classLoader) { + Assert.notNull(annotationType, "'annotationType' must not be null"); + this.annotationType = getAnnotationType(annotationType, classLoader); + this.displayName = annotationType; + this.validated = false; + } @SuppressWarnings("unchecked") @Nullable @@ -349,39 +351,29 @@ public A[] getAnnotationArray(String attributeName, Class private T getRequiredAttribute(String attributeName, Class expectedType) { Assert.hasText(attributeName, "'attributeName' must not be null or empty"); Object value = get(attributeName); - assertAttributePresence(attributeName, value); - assertNotException(attributeName, value); + if (value == null) { + throw new IllegalArgumentException(String.format( + "Attribute '%s' not found in attributes for annotation [%s]", + attributeName, this.displayName)); + } + if (value instanceof Throwable throwable) { + throw new IllegalArgumentException(String.format( + "Attribute '%s' for annotation [%s] was not resolvable due to exception [%s]", + attributeName, this.displayName, value), throwable); + } if (!expectedType.isInstance(value) && expectedType.isArray() && expectedType.componentType().isInstance(value)) { Object array = Array.newInstance(expectedType.componentType(), 1); Array.set(array, 0, value); value = array; } - assertAttributeType(attributeName, value, expectedType); - return (T) value; - } - - private void assertAttributePresence(String attributeName, Object attributeValue) { - Assert.notNull(attributeValue, () -> String.format( - "Attribute '%s' not found in attributes for annotation [%s]", - attributeName, this.displayName)); - } - - private void assertNotException(String attributeName, Object attributeValue) { - if (attributeValue instanceof Throwable throwable) { - throw new IllegalArgumentException(String.format( - "Attribute '%s' for annotation [%s] was not resolvable due to exception [%s]", - attributeName, this.displayName, attributeValue), throwable); - } - } - - private void assertAttributeType(String attributeName, Object attributeValue, Class expectedType) { - if (!expectedType.isInstance(attributeValue)) { + if (!expectedType.isInstance(value)) { throw new IllegalArgumentException(String.format( "Attribute '%s' is of type %s, but %s was expected in attributes for annotation [%s]", - attributeName, attributeValue.getClass().getSimpleName(), expectedType.getSimpleName(), + attributeName, value.getClass().getSimpleName(), expectedType.getSimpleName(), this.displayName)); } + return (T) value; } @Override diff --git a/spring-core/src/main/java/org/springframework/core/annotation/AnnotationUtils.java b/spring-core/src/main/java/org/springframework/core/annotation/AnnotationUtils.java index 89741e14151b..02cda0908906 100644 --- a/spring-core/src/main/java/org/springframework/core/annotation/AnnotationUtils.java +++ b/spring-core/src/main/java/org/springframework/core/annotation/AnnotationUtils.java @@ -985,8 +985,12 @@ public static void postProcessAnnotationAttributes(@Nullable Object annotatedEle } } - private static Object getAttributeValueForMirrorResolution(Method attribute, Object attributes) { - Object result = ((AnnotationAttributes) attributes).get(attribute.getName()); + @Nullable + private static Object getAttributeValueForMirrorResolution(Method attribute, @Nullable Object attributes) { + if (!(attributes instanceof AnnotationAttributes annotationAttributes)) { + return null; + } + Object result = annotationAttributes.get(attribute.getName()); return (result instanceof DefaultValueHolder defaultValueHolder ? defaultValueHolder.defaultValue : result); } diff --git a/spring-core/src/main/java/org/springframework/core/annotation/AnnotationsScanner.java b/spring-core/src/main/java/org/springframework/core/annotation/AnnotationsScanner.java index 6a913b250df9..5bd7168f96a1 100644 --- a/spring-core/src/main/java/org/springframework/core/annotation/AnnotationsScanner.java +++ b/spring-core/src/main/java/org/springframework/core/annotation/AnnotationsScanner.java @@ -127,29 +127,31 @@ private static R processClassInheritedAnnotations(C context, Class sou if (result != null) { return result; } - Annotation[] declaredAnnotations = getDeclaredAnnotations(source, true); - if (relevant == null && declaredAnnotations.length > 0) { - relevant = root.getAnnotations(); - remaining = relevant.length; - } - for (int i = 0; i < declaredAnnotations.length; i++) { - if (declaredAnnotations[i] != null) { - boolean isRelevant = false; - for (int relevantIndex = 0; relevantIndex < relevant.length; relevantIndex++) { - if (relevant[relevantIndex] != null && - declaredAnnotations[i].annotationType() == relevant[relevantIndex].annotationType()) { - isRelevant = true; - relevant[relevantIndex] = null; - remaining--; - break; + Annotation[] declaredAnns = getDeclaredAnnotations(source, true); + if (declaredAnns.length > 0) { + if (relevant == null) { + relevant = root.getAnnotations(); + remaining = relevant.length; + } + for (int i = 0; i < declaredAnns.length; i++) { + if (declaredAnns[i] != null) { + boolean isRelevant = false; + for (int relevantIndex = 0; relevantIndex < relevant.length; relevantIndex++) { + if (relevant[relevantIndex] != null && + declaredAnns[i].annotationType() == relevant[relevantIndex].annotationType()) { + isRelevant = true; + relevant[relevantIndex] = null; + remaining--; + break; + } + } + if (!isRelevant) { + declaredAnns[i] = null; } - } - if (!isRelevant) { - declaredAnnotations[i] = null; } } } - result = processor.doWithAnnotations(context, aggregateIndex, source, declaredAnnotations); + result = processor.doWithAnnotations(context, aggregateIndex, source, declaredAnns); if (result != null) { return result; } diff --git a/spring-core/src/main/java/org/springframework/core/annotation/TypeMappedAnnotation.java b/spring-core/src/main/java/org/springframework/core/annotation/TypeMappedAnnotation.java index 85a0e52a9642..1d0d093aa2c1 100644 --- a/spring-core/src/main/java/org/springframework/core/annotation/TypeMappedAnnotation.java +++ b/spring-core/src/main/java/org/springframework/core/annotation/TypeMappedAnnotation.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2023 the original author or authors. + * Copyright 2002-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -322,12 +322,13 @@ private > Object adaptValueForMapOptions(Method at @SuppressWarnings("unchecked") protected A createSynthesizedAnnotation() { // Check root annotation - if (isTargetAnnotation(this.rootAttributes) && !isSynthesizable((Annotation) this.rootAttributes)) { - return (A) this.rootAttributes; + if (this.rootAttributes instanceof Annotation ann && isTargetAnnotation(ann) && !isSynthesizable(ann)) { + return (A) ann; } // Check meta-annotation - else if (isTargetAnnotation(this.mapping.getAnnotation()) && !isSynthesizable(this.mapping.getAnnotation())) { - return (A) this.mapping.getAnnotation(); + Annotation meta = this.mapping.getAnnotation(); + if (meta != null && isTargetAnnotation(meta) && !isSynthesizable(meta)) { + return (A) meta; } return SynthesizedMergedAnnotationInvocationHandler.createProxy(this, getType()); } @@ -338,7 +339,7 @@ else if (isTargetAnnotation(this.mapping.getAnnotation()) && !isSynthesizable(th * @param obj the object to check * @since 5.3.22 */ - private boolean isTargetAnnotation(@Nullable Object obj) { + private boolean isTargetAnnotation(Object obj) { return getType().isInstance(obj); } @@ -432,7 +433,7 @@ private Object getValueFromMetaAnnotation(int attributeIndex, boolean forMirrorR } @Nullable - private Object getValueForMirrorResolution(Method attribute, Object annotation) { + private Object getValueForMirrorResolution(Method attribute, @Nullable Object annotation) { int attributeIndex = this.mapping.getAttributes().indexOf(attribute); boolean valueAttribute = VALUE.equals(attribute.getName()); return getValue(attributeIndex, !valueAttribute, true); diff --git a/spring-core/src/main/java/org/springframework/core/codec/Decoder.java b/spring-core/src/main/java/org/springframework/core/codec/Decoder.java index d37244b764d5..f49063f101ae 100644 --- a/spring-core/src/main/java/org/springframework/core/codec/Decoder.java +++ b/spring-core/src/main/java/org/springframework/core/codec/Decoder.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2023 the original author or authors. + * Copyright 2002-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -95,20 +95,19 @@ default T decode(DataBuffer buffer, ResolvableType targetType, @Nullable MimeType mimeType, @Nullable Map hints) throws DecodingException { CompletableFuture future = decodeToMono(Mono.just(buffer), targetType, mimeType, hints).toFuture(); - Assert.state(future.isDone(), "DataBuffer decoding should have completed."); + Assert.state(future.isDone(), "DataBuffer decoding should have completed"); - Throwable failure; try { return future.get(); } catch (ExecutionException ex) { - failure = ex.getCause(); + Throwable cause = ex.getCause(); + throw (cause instanceof CodecException codecException ? codecException : + new DecodingException("Failed to decode: " + (cause != null ? cause.getMessage() : ex), cause)); } catch (InterruptedException ex) { - failure = ex; + throw new DecodingException("Interrupted during decode", ex); } - throw (failure instanceof CodecException codecException ? codecException : - new DecodingException("Failed to decode: " + failure.getMessage(), failure)); } /** diff --git a/spring-core/src/main/java/org/springframework/core/codec/ResourceDecoder.java b/spring-core/src/main/java/org/springframework/core/codec/ResourceDecoder.java index 23e5f5f6e6d5..f9d92f5e6e52 100644 --- a/spring-core/src/main/java/org/springframework/core/codec/ResourceDecoder.java +++ b/spring-core/src/main/java/org/springframework/core/codec/ResourceDecoder.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2023 the original author or authors. + * Copyright 2002-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -80,6 +80,7 @@ public Resource decode(DataBuffer dataBuffer, ResolvableType elementType, if (clazz == InputStreamResource.class) { return new InputStreamResource(new ByteArrayInputStream(bytes)) { @Override + @Nullable public String getFilename() { return filename; } @@ -92,6 +93,7 @@ public long contentLength() { else if (Resource.class.isAssignableFrom(clazz)) { return new ByteArrayResource(bytes) { @Override + @Nullable public String getFilename() { return filename; } diff --git a/spring-core/src/main/java/org/springframework/core/convert/support/ObjectToObjectConverter.java b/spring-core/src/main/java/org/springframework/core/convert/support/ObjectToObjectConverter.java index 5b7cccba95f0..b65f11f24f0a 100644 --- a/spring-core/src/main/java/org/springframework/core/convert/support/ObjectToObjectConverter.java +++ b/spring-core/src/main/java/org/springframework/core/convert/support/ObjectToObjectConverter.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2022 the original author or authors. + * Copyright 2002-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -136,7 +136,7 @@ static boolean hasConversionMethodOrConstructor(Class targetClass, Class s @Nullable private static Executable getValidatedExecutable(Class targetClass, Class sourceClass) { Executable executable = conversionExecutableCache.get(targetClass); - if (isApplicable(executable, sourceClass)) { + if (executable != null && isApplicable(executable, sourceClass)) { return executable; } diff --git a/spring-core/src/main/java/org/springframework/core/convert/support/StringToPatternConverter.java b/spring-core/src/main/java/org/springframework/core/convert/support/StringToPatternConverter.java index 689b48a0b1c5..ca2634b2117b 100644 --- a/spring-core/src/main/java/org/springframework/core/convert/support/StringToPatternConverter.java +++ b/spring-core/src/main/java/org/springframework/core/convert/support/StringToPatternConverter.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2023 the original author or authors. + * Copyright 2002-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,6 +19,7 @@ import java.util.regex.Pattern; import org.springframework.core.convert.converter.Converter; +import org.springframework.lang.Nullable; /** * Converts from a String to a {@link java.util.regex.Pattern}. @@ -30,6 +31,7 @@ final class StringToPatternConverter implements Converter { @Override + @Nullable public Pattern convert(String source) { if (source.isEmpty()) { return null; diff --git a/spring-core/src/main/java/org/springframework/core/convert/support/StringToRegexConverter.java b/spring-core/src/main/java/org/springframework/core/convert/support/StringToRegexConverter.java index 96e49c71071c..93d96314241e 100644 --- a/spring-core/src/main/java/org/springframework/core/convert/support/StringToRegexConverter.java +++ b/spring-core/src/main/java/org/springframework/core/convert/support/StringToRegexConverter.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2023 the original author or authors. + * Copyright 2002-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,16 +19,19 @@ import kotlin.text.Regex; import org.springframework.core.convert.converter.Converter; +import org.springframework.lang.Nullable; /** * Converts from a String to a Kotlin {@link Regex}. * * @author Stephane Nicoll * @author Sebastien Deleuze + * @since 6.1 */ class StringToRegexConverter implements Converter { @Override + @Nullable public Regex convert(String source) { if (source.isEmpty()) { return null; diff --git a/spring-core/src/main/java/org/springframework/core/io/buffer/DataBufferUtils.java b/spring-core/src/main/java/org/springframework/core/io/buffer/DataBufferUtils.java index 0d7b1d6393b8..b5ed08ad6093 100644 --- a/spring-core/src/main/java/org/springframework/core/io/buffer/DataBufferUtils.java +++ b/spring-core/src/main/java/org/springframework/core/io/buffer/DataBufferUtils.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2023 the original author or authors. + * Copyright 2002-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -71,7 +71,6 @@ public abstract class DataBufferUtils { private static final int DEFAULT_CHUNK_SIZE = 1024; - //--------------------------------------------------------------------- // Reading //--------------------------------------------------------------------- @@ -1083,7 +1082,7 @@ public void completed(Integer read, Attachment attachment) { attachment.iterator().close(); DataBuffer dataBuffer = attachment.dataBuffer(); - if (this.state.get().equals(State.DISPOSED)) { + if (this.state.get() == State.DISPOSED) { release(dataBuffer); closeChannel(this.channel); return; diff --git a/spring-core/src/main/java/org/springframework/core/metrics/DefaultApplicationStartup.java b/spring-core/src/main/java/org/springframework/core/metrics/DefaultApplicationStartup.java index 9fdb24ac9ed3..9a497d3eb9ee 100644 --- a/spring-core/src/main/java/org/springframework/core/metrics/DefaultApplicationStartup.java +++ b/spring-core/src/main/java/org/springframework/core/metrics/DefaultApplicationStartup.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2021 the original author or authors. + * Copyright 2002-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,6 +20,8 @@ import java.util.Iterator; import java.util.function.Supplier; +import org.springframework.lang.Nullable; + /** * Default "no op" {@code ApplicationStartup} implementation. * @@ -52,6 +54,7 @@ public long getId() { } @Override + @Nullable public Long getParentId() { return null; } @@ -73,7 +76,6 @@ public StartupStep tag(String key, Supplier value) { @Override public void end() { - } diff --git a/spring-core/src/main/java/org/springframework/core/type/filter/AbstractTypeHierarchyTraversingFilter.java b/spring-core/src/main/java/org/springframework/core/type/filter/AbstractTypeHierarchyTraversingFilter.java index 145450bf5795..ee7d6d5046e2 100644 --- a/spring-core/src/main/java/org/springframework/core/type/filter/AbstractTypeHierarchyTraversingFilter.java +++ b/spring-core/src/main/java/org/springframework/core/type/filter/AbstractTypeHierarchyTraversingFilter.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2023 the original author or authors. + * Copyright 2002-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -80,13 +80,13 @@ public boolean match(MetadataReader metadataReader, MetadataReaderFactory metada else { // Need to read superclass to determine a match... try { - if (match(metadata.getSuperClassName(), metadataReaderFactory)) { + if (match(superClassName, metadataReaderFactory)) { return true; } } catch (IOException ex) { if (logger.isDebugEnabled()) { - logger.debug("Could not read superclass [" + metadata.getSuperClassName() + + logger.debug("Could not read superclass [" + superClassName + "] of type-filtered class [" + metadata.getClassName() + "]"); } }