-
Notifications
You must be signed in to change notification settings - Fork 38k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Create a new API for handling merged annotations
Add new `MergedAnnotations` and `MergedAnnotation` interfaces that attempt to provide a uniform way for dealing with merged annotations. Specifically, the new API provides alternatives for the static methods in `AnnotationUtils` and `AnnotatedElementUtils` and supports Spring's comprehensive annotation attribute `@AliasFor` features. The interfaces also open the possibility of the same API being exposed from the `AnnotationMetadata` interface. Additional utility classes for collecting, filtering and selecting annotations have also been added. Typical usage for the new API would be something like: MergedAnnotations.from(Example.class) .stream(MyAnnotation.class) .map(a -> a.getString("value")) .forEach(System.out::println); Closes gh-21697
- Loading branch information
Showing
35 changed files
with
13,436 additions
and
0 deletions.
There are no files selected for viewing
226 changes: 226 additions & 0 deletions
226
spring-core/src/main/java/org/springframework/core/annotation/AbstractMergedAnnotation.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,226 @@ | ||
/* | ||
* Copyright 2002-2019 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 | ||
* | ||
* http://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.lang.annotation.Annotation; | ||
import java.lang.reflect.Array; | ||
import java.util.Map; | ||
import java.util.NoSuchElementException; | ||
import java.util.Optional; | ||
import java.util.function.Predicate; | ||
|
||
import org.springframework.lang.Nullable; | ||
import org.springframework.util.Assert; | ||
|
||
/** | ||
* Abstract base class for {@link MergedAnnotation} implementations. | ||
* | ||
* @author Phillip Webb | ||
* @since 5.2 | ||
* @param <A> the annotation type | ||
*/ | ||
abstract class AbstractMergedAnnotation<A extends Annotation> | ||
implements MergedAnnotation<A> { | ||
|
||
@Nullable | ||
private volatile A synthesizedAnnotation; | ||
|
||
|
||
@Override | ||
public boolean isDirectlyPresent() { | ||
return isPresent() && getDepth() == 0; | ||
} | ||
|
||
@Override | ||
public boolean isMetaPresent() { | ||
return isPresent() && getDepth() > 0; | ||
} | ||
|
||
@Override | ||
public boolean hasNonDefaultValue(String attributeName) { | ||
return !hasDefaultValue(attributeName); | ||
} | ||
|
||
public byte getByte(String attributeName) { | ||
return getRequiredAttributeValue(attributeName, Byte.class); | ||
} | ||
|
||
public byte[] getByteArray(String attributeName) { | ||
return getRequiredAttributeValue(attributeName, byte[].class); | ||
} | ||
|
||
public boolean getBoolean(String attributeName) { | ||
return getRequiredAttributeValue(attributeName, Boolean.class); | ||
} | ||
|
||
public boolean[] getBooleanArray(String attributeName) { | ||
return getRequiredAttributeValue(attributeName, boolean[].class); | ||
} | ||
|
||
public char getChar(String attributeName) { | ||
return getRequiredAttributeValue(attributeName, Character.class); | ||
} | ||
|
||
public char[] getCharArray(String attributeName) { | ||
return getRequiredAttributeValue(attributeName, char[].class); | ||
} | ||
|
||
public short getShort(String attributeName) { | ||
return getRequiredAttributeValue(attributeName, Short.class); | ||
} | ||
|
||
public short[] getShortArray(String attributeName) { | ||
return getRequiredAttributeValue(attributeName, short[].class); | ||
} | ||
|
||
public int getInt(String attributeName) { | ||
return getRequiredAttributeValue(attributeName, Integer.class); | ||
} | ||
|
||
public int[] getIntArray(String attributeName) { | ||
return getRequiredAttributeValue(attributeName, int[].class); | ||
} | ||
|
||
public long getLong(String attributeName) { | ||
return getRequiredAttributeValue(attributeName, Long.class); | ||
} | ||
|
||
public long[] getLongArray(String attributeName) { | ||
return getRequiredAttributeValue(attributeName, long[].class); | ||
} | ||
|
||
public double getDouble(String attributeName) { | ||
return getRequiredAttributeValue(attributeName, Double.class); | ||
} | ||
|
||
public double[] getDoubleArray(String attributeName) { | ||
return getRequiredAttributeValue(attributeName, double[].class); | ||
} | ||
|
||
public float getFloat(String attributeName) { | ||
return getRequiredAttributeValue(attributeName, Float.class); | ||
} | ||
|
||
public float[] getFloatArray(String attributeName) { | ||
return getRequiredAttributeValue(attributeName, float[].class); | ||
} | ||
|
||
public String getString(String attributeName) { | ||
return getRequiredAttributeValue(attributeName, String.class); | ||
} | ||
|
||
public String[] getStringArray(String attributeName) { | ||
return getRequiredAttributeValue(attributeName, String[].class); | ||
} | ||
|
||
public Class<?> getClass(String attributeName) { | ||
return getRequiredAttributeValue(attributeName, Class.class); | ||
} | ||
|
||
public Class<?>[] getClassArray(String attributeName) { | ||
return getRequiredAttributeValue(attributeName, Class[].class); | ||
} | ||
|
||
public <E extends Enum<E>> E getEnum(String attributeName, Class<E> type) { | ||
Assert.notNull(type, "Type must not be null"); | ||
return getRequiredAttributeValue(attributeName, type); | ||
} | ||
|
||
@SuppressWarnings("unchecked") | ||
public <E extends Enum<E>> E[] getEnumArray(String attributeName, Class<E> type) { | ||
Assert.notNull(type, "Type must not be null"); | ||
Class<?> arrayType = Array.newInstance(type, 0).getClass(); | ||
return (E[]) getRequiredAttributeValue(attributeName, arrayType); | ||
} | ||
|
||
@Override | ||
public Optional<Object> getValue(String attributeName) { | ||
return getValue(attributeName, Object.class); | ||
} | ||
|
||
@Override | ||
public <T> Optional<T> getValue(String attributeName, Class<T> type) { | ||
return Optional.ofNullable(getAttributeValue(attributeName, type)); | ||
} | ||
|
||
@Override | ||
public Optional<Object> getDefaultValue(String attributeName) { | ||
return getDefaultValue(attributeName, Object.class); | ||
} | ||
|
||
@Override | ||
public MergedAnnotation<A> filterDefaultValues() { | ||
return filterAttributes(this::hasNonDefaultValue); | ||
} | ||
|
||
@Override | ||
public Map<String, Object> asMap(MapValues... options) { | ||
return asMap(null, options); | ||
} | ||
|
||
@Override | ||
public Optional<A> synthesize( | ||
@Nullable Predicate<? super MergedAnnotation<A>> condition) | ||
throws NoSuchElementException { | ||
|
||
if (condition == null || condition.test(this)) { | ||
return Optional.of(synthesize()); | ||
} | ||
return Optional.empty(); | ||
} | ||
|
||
@Override | ||
public A synthesize() { | ||
if (!isPresent()) { | ||
throw new NoSuchElementException("Unable to synthesize missing annotation"); | ||
} | ||
A synthesized = this.synthesizedAnnotation; | ||
if (synthesized == null) { | ||
synthesized = createSynthesized(); | ||
this.synthesizedAnnotation = synthesized; | ||
} | ||
return synthesized; | ||
} | ||
|
||
private <T> T getRequiredAttributeValue(String attributeName, Class<T> type) { | ||
T value = getAttributeValue(attributeName, type); | ||
if (value == null) { | ||
throw new NoSuchElementException("No attribute named '" + attributeName + | ||
"' present in merged annotation " + getType()); | ||
} | ||
return value; | ||
} | ||
|
||
/** | ||
* Get the underlying attribute value. | ||
* @param attributeName the attribute name | ||
* @param type the type to return (see {@link MergedAnnotation} class | ||
* documentation for details). | ||
* @return the attribute value or {@code null} if the value is not found and | ||
* is not required | ||
* @throws IllegalArgumentException if the source type is not compatible | ||
* @throws NoSuchElementException if the value is required but not found | ||
*/ | ||
@Nullable | ||
protected abstract <T> T getAttributeValue(String attributeName, Class<T> type); | ||
|
||
/** | ||
* Factory method used to create the synthesized annotation. | ||
*/ | ||
protected abstract A createSynthesized(); | ||
|
||
} |
Oops, something went wrong.