/**
* Copyright 2013 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 io.neba.core.util;
import java.lang.annotation.Annotation;
import java.lang.reflect.AnnotatedElement;
import java.util.*;
import static io.neba.core.util.ReadOnlyIterator.readOnly;
import static java.util.Arrays.asList;
import static java.util.Collections.addAll;
/**
* Supports meta-annotations by looking up annotations in the transitive
* hull (annotations and their annotations, called meta-annotations) of a given
* {@link java.lang.reflect.AnnotatedElement}.
*
* @author Olaf Otto
*/
public class Annotations implements Iterable<Annotation> {
private final AnnotatedElement annotatedElement;
private Map<Class<? extends Annotation>, Annotation> annotations = null;
/**
* @param annotatedElement must not be <code>null</code>
* @return never null.
*/
public static Annotations annotations(AnnotatedElement annotatedElement) {
if (annotatedElement == null) {
throw new IllegalArgumentException("Method argument annotatedElement must not be null.");
}
return new Annotations(annotatedElement);
}
/**
* @param annotatedElement must not be <code>null</code>.
*/
public Annotations(AnnotatedElement annotatedElement) {
if (annotatedElement == null) {
throw new IllegalArgumentException("Constructor parameter annotatedElement must not be null.");
}
this.annotatedElement = annotatedElement;
}
/**
* @param type must not be <code>null</code>.
* @return whether the given element or any of its meta-annotations is annotated with the given annotation type.
*/
public boolean contains(Class<? extends Annotation> type) {
if (type == null) {
throw new IllegalArgumentException("Method argument type must not be null.");
}
return getAnnotationMap().get(type) != null;
}
/**
* @param name must not be <code>null</code>.
* @return whether the given type name matches one of the present annotations
*/
public boolean containsName(String name) {
if (name == null) {
throw new IllegalArgumentException("Method argument name must not be null.");
}
for (Class<? extends Annotation> annotationType : getAnnotationMap().keySet()) {
if (annotationType.getName().equals(name)) {
return true;
}
}
return false;
}
/**
* @param type must not be <code>null</code>.
* @return the annotation if present on the given element or any meta-annotation thereof, or <code>null</code>.
*/
@SuppressWarnings("unchecked")
public <T extends Annotation> T get(Class<T> type) {
if (type == null) {
throw new IllegalArgumentException("Method argument type must not be null.");
}
return (T) getAnnotationMap().get(type);
}
/**
* @return all annotations and meta-annotations present on the element. Never <code>null</code> but rather an empty map.
*/
private Map<Class<? extends Annotation>, Annotation> getAnnotationMap() {
if (this.annotations == null) {
// We do not care about calculating the same thing twice in case of concurrent access.
HashMap<Class<? extends Annotation>, Annotation> annotations = new HashMap<>();
Queue<Annotation> queue = new LinkedList<>(asList(this.annotatedElement.getAnnotations()));
while (!queue.isEmpty()) {
Annotation annotation = queue.remove();
// Prevent lookup loops (@A annotated with @B annotated with @A ...)
if (!annotations.containsKey(annotation.annotationType())) {
annotations.put(annotation.annotationType(), annotation);
addAll(queue, annotation.annotationType().getAnnotations());
}
}
this.annotations = annotations;
}
return this.annotations;
}
@Override
public Iterator<Annotation> iterator() {
return readOnly(getAnnotationMap().values().iterator());
}
public Map<Class<? extends Annotation>, Annotation> getAnnotations() {
return new HashMap<>(getAnnotationMap());
}
}