Skip to content

Commit

Permalink
Create a new API for handling merged annotations
Browse files Browse the repository at this point in the history
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
philwebb authored and jhoeller committed Mar 22, 2019
1 parent fdacda8 commit 4972d85
Show file tree
Hide file tree
Showing 35 changed files with 13,436 additions and 0 deletions.
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();

}
Loading

0 comments on commit 4972d85

Please sign in to comment.