diff --git a/spring-core/src/main/java/org/springframework/core/annotation/AnnotatedElementAdapter.java b/spring-core/src/main/java/org/springframework/core/annotation/AnnotatedElementAdapter.java new file mode 100644 index 0000000000..f6456d879c --- /dev/null +++ b/spring-core/src/main/java/org/springframework/core/annotation/AnnotatedElementAdapter.java @@ -0,0 +1,130 @@ +/* + * Copyright 2002-2025 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. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.core.annotation; + +import java.io.Serializable; +import java.lang.annotation.Annotation; +import java.lang.reflect.AnnotatedElement; +import java.util.Arrays; + +import org.jspecify.annotations.Nullable; + +/** + * Adapter for exposing a set of annotations as an {@link AnnotatedElement}, in + * particular as input for various methods in {@link AnnotatedElementUtils}. + * + * @author Juergen Hoeller + * @author Sam Brannen + * @since 7.0 + * @see #from(Annotation[]) + * @see AnnotatedElementUtils#isAnnotated(AnnotatedElement, Class) + * @see AnnotatedElementUtils#getMergedAnnotation(AnnotatedElement, Class) + */ +@SuppressWarnings("serial") +public final class AnnotatedElementAdapter implements AnnotatedElement, Serializable { + + private static final AnnotatedElementAdapter EMPTY = new AnnotatedElementAdapter(new Annotation[0]); + + + /** + * Create an {@code AnnotatedElementAdapter} from the supplied annotations. + *

The supplied annotations will be considered to be both present + * and directly present with regard to the results returned from + * methods such as {@link #getAnnotation(Class)}, + * {@link #getDeclaredAnnotation(Class)}, etc. + *

If the supplied annotations array is either {@code null} or empty, this + * factory method will return an {@linkplain #isEmpty() empty} adapter. + * @param annotations the annotations to expose via the {@link AnnotatedElement} + * API + * @return a new {@code AnnotatedElementAdapter} + */ + public static AnnotatedElementAdapter from(Annotation @Nullable [] annotations) { + if (annotations == null || annotations.length == 0) { + return EMPTY; + } + return new AnnotatedElementAdapter(annotations); + } + + + private final Annotation[] annotations; + + + private AnnotatedElementAdapter(Annotation[] annotations) { + this.annotations = annotations; + } + + + @Override + public boolean isAnnotationPresent(Class annotationClass) { + for (Annotation annotation : this.annotations) { + if (annotation.annotationType() == annotationClass) { + return true; + } + } + return false; + } + + @Override + public @Nullable A getAnnotation(Class annotationClass) { + for (Annotation annotation : this.annotations) { + if (annotation.annotationType() == annotationClass) { + return annotationClass.cast(annotation); + } + } + return null; + } + + @Override + public Annotation[] getAnnotations() { + return (isEmpty() ? this.annotations : this.annotations.clone()); + } + + @Override + public @Nullable A getDeclaredAnnotation(Class annotationClass) { + return getAnnotation(annotationClass); + } + + @Override + public Annotation[] getDeclaredAnnotations() { + return getAnnotations(); + } + + /** + * Determine if this {@code AnnotatedElementAdapter} is empty. + * @return {@code true} if this adapter contains no annotations + */ + public boolean isEmpty() { + return (this == EMPTY); + } + + @Override + public boolean equals(@Nullable Object other) { + return (this == other || (other instanceof AnnotatedElementAdapter that && + Arrays.equals(this.annotations, that.annotations))); + } + + @Override + public int hashCode() { + return Arrays.hashCode(this.annotations); + } + + @Override + public String toString() { + return Arrays.toString(this.annotations); + } + +} diff --git a/spring-core/src/main/java/org/springframework/core/annotation/AnnotatedElementUtils.java b/spring-core/src/main/java/org/springframework/core/annotation/AnnotatedElementUtils.java index 80e044a45a..b79a97b47d 100644 --- a/spring-core/src/main/java/org/springframework/core/annotation/AnnotatedElementUtils.java +++ b/spring-core/src/main/java/org/springframework/core/annotation/AnnotatedElementUtils.java @@ -100,12 +100,12 @@ public abstract class AnnotatedElementUtils { /** * Build an adapted {@link AnnotatedElement} for the given annotations, - * typically for use with other methods on {@link AnnotatedElementUtils}. + * typically for use with other methods in {@link AnnotatedElementUtils}. * @param annotations the annotations to expose through the {@code AnnotatedElement} * @since 4.3 */ public static AnnotatedElement forAnnotations(Annotation... annotations) { - return new AnnotatedElementForAnnotations(annotations); + return AnnotatedElementAdapter.from(annotations); } /** @@ -837,39 +837,4 @@ public abstract class AnnotatedElementUtils { return annotation.asAnnotationAttributes(Adapt.values(classValuesAsString, nestedAnnotationsAsMap)); } - - /** - * Adapted {@link AnnotatedElement} that holds specific annotations. - */ - private static class AnnotatedElementForAnnotations implements AnnotatedElement { - - private final Annotation[] annotations; - - AnnotatedElementForAnnotations(Annotation... annotations) { - this.annotations = annotations; - } - - @Override - @SuppressWarnings("unchecked") - public @Nullable T getAnnotation(Class annotationClass) { - for (Annotation annotation : this.annotations) { - if (annotation.annotationType() == annotationClass) { - return (T) annotation; - } - } - return null; - } - - @Override - public Annotation[] getAnnotations() { - return this.annotations.clone(); - } - - @Override - public Annotation[] getDeclaredAnnotations() { - return this.annotations.clone(); - } - - } - } diff --git a/spring-core/src/main/java/org/springframework/core/convert/TypeDescriptor.java b/spring-core/src/main/java/org/springframework/core/convert/TypeDescriptor.java index 3efe15ffcf..d83a6fb3d4 100644 --- a/spring-core/src/main/java/org/springframework/core/convert/TypeDescriptor.java +++ b/spring-core/src/main/java/org/springframework/core/convert/TypeDescriptor.java @@ -18,7 +18,6 @@ package org.springframework.core.convert; import java.io.Serializable; import java.lang.annotation.Annotation; -import java.lang.reflect.AnnotatedElement; import java.lang.reflect.Field; import java.lang.reflect.Type; import java.util.Arrays; @@ -32,6 +31,7 @@ import org.jspecify.annotations.Nullable; import org.springframework.core.MethodParameter; import org.springframework.core.ResolvableType; +import org.springframework.core.annotation.AnnotatedElementAdapter; import org.springframework.core.annotation.AnnotatedElementUtils; import org.springframework.lang.Contract; import org.springframework.util.Assert; @@ -39,6 +39,7 @@ import org.springframework.util.ClassUtils; /** * Contextual descriptor about a type to convert from or to. + * *

Capable of representing arrays and generic collection types. * * @author Keith Donald @@ -731,82 +732,6 @@ public class TypeDescriptor implements Serializable { } - /** - * Adapter class for exposing a {@code TypeDescriptor}'s annotations as an - * {@link AnnotatedElement}, in particular to {@link AnnotatedElementUtils}. - * @see AnnotatedElementUtils#isAnnotated(AnnotatedElement, Class) - * @see AnnotatedElementUtils#getMergedAnnotation(AnnotatedElement, Class) - */ - private static final class AnnotatedElementAdapter implements AnnotatedElement, Serializable { - - private static final AnnotatedElementAdapter EMPTY = new AnnotatedElementAdapter(new Annotation[0]); - - private final Annotation[] annotations; - - private AnnotatedElementAdapter(Annotation[] annotations) { - this.annotations = annotations; - } - - private static AnnotatedElementAdapter from(Annotation @Nullable [] annotations) { - if (annotations == null || annotations.length == 0) { - return EMPTY; - } - return new AnnotatedElementAdapter(annotations); - } - - @Override - public boolean isAnnotationPresent(Class annotationClass) { - for (Annotation annotation : this.annotations) { - if (annotation.annotationType() == annotationClass) { - return true; - } - } - return false; - } - - @Override - @SuppressWarnings("unchecked") - public @Nullable T getAnnotation(Class annotationClass) { - for (Annotation annotation : this.annotations) { - if (annotation.annotationType() == annotationClass) { - return (T) annotation; - } - } - return null; - } - - @Override - public Annotation[] getAnnotations() { - return (isEmpty() ? this.annotations : this.annotations.clone()); - } - - @Override - public Annotation[] getDeclaredAnnotations() { - return getAnnotations(); - } - - public boolean isEmpty() { - return (this.annotations.length == 0); - } - - @Override - public boolean equals(@Nullable Object other) { - return (this == other || (other instanceof AnnotatedElementAdapter that && - Arrays.equals(this.annotations, that.annotations))); - } - - @Override - public int hashCode() { - return Arrays.hashCode(this.annotations); - } - - @Override - public String toString() { - return Arrays.toString(this.annotations); - } - } - - private interface AnnotatedElementSupplier extends Supplier, Serializable { }