org.glassfish.pfl.basic.algorithm.AnnotationAnalyzer Maven / Gradle / Ivy
/*
* Copyright (c) 2011, 2019 Oracle and/or its affiliates. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Distribution License v. 1.0, which is available at
* http://www.eclipse.org/org/documents/edl-v10.php.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
package org.glassfish.pfl.basic.algorithm;
import java.lang.annotation.Annotation;
import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.WeakHashMap;
import org.glassfish.pfl.basic.contain.Pair;
import org.glassfish.pfl.basic.func.UnaryPredicate;
/** Given an AnnotatedElement, fetch all of the inherited annotations.
* This include annotations on methods that may be
* overridden. Uses ClassAnalyzer to linearize the inheritance hierarchy.
* This also includes any added annotations for Class and Method.
* Constructor, Field, Parameter, and Package annotations are just translated
* from the standard reflective calls.
*
* @author ken_admin
*/
public class AnnotationAnalyzer {
private final Map,Annotation>> annotationCache =
new WeakHashMap,Annotation>>() ;
private final Map, Annotation>> addedAnnotations =
new HashMap,Annotation>>();
private synchronized void putIfNotPresent( final Map map,
final K key, final V value ) {
if (!map.containsKey( key )) {
map.put( key, value ) ;
}
}
/** Add an annotation to element, which must be a Class, Method,
* or Constructor.
*
* @param element
* @param annotation
*/
public synchronized void addAnnotation( AnnotatedElement element,
Annotation annotation ) {
if (annotation == null) {
throw new RuntimeException( "Cannot add null annotation to "
+ "annotated element " + element ) ;
}
Map, Annotation> map = addedAnnotations.get( element ) ;
if (map == null) {
map = new HashMap, Annotation>() ;
addedAnnotations.put( element, map ) ;
}
Class> annotationType = annotation.annotationType() ;
Annotation ann = map.get( annotationType ) ;
if (ann != null) {
throw new RuntimeException( "Duplicate annotatio "
+ annotation.getClass().getName()
+ " for element " + element ) ;
}
map.put( annotationType, annotation ) ;
}
/** Add all annotations on cls (including inherited annotations
* and its methods (including overridden methods in super classes and
* interfaces) to super (which must be a super class or interface of cls).
*
* @param cls
*/
public synchronized void addInheritedAnnotations( final Class> cls,
final Class> ancestor ) {
if (!ancestor.isAssignableFrom(cls)) {
throw new RuntimeException( "Ancestor " + ancestor
+ " is not assignment compatible with " + cls ) ;
}
// added class annotations
final Map,Annotation> classAnnos =
getAnnotations( cls, false ) ;
addedAnnotations.put( ancestor, classAnnos ) ;
// added method annotations
final ClassAnalyzer clsCA = ClassAnalyzer.getClassAnalyzer(cls) ;
final ClassAnalyzer ancestorCA =
ClassAnalyzer.getClassAnalyzer( ancestor );
// Just construct a list of all reachable classes from ancestor.
final Set> ancestorClasses =
new HashSet>() ;
ancestorCA.findClasses( new UnaryPredicate>() {
@Override
public boolean evaluate( final Class> arg) {
ancestorClasses.add(arg) ;
return true ;
}
});
// Construct a map from method type to annotation map for
// all method types found in classes reachable from cls but not
// from ancestor.
final Map>>,Map,Annotation>> map =
new HashMap>>,Map,Annotation>>() ;
clsCA.findMethods( new UnaryPredicate() {
@Override
public boolean evaluate(Method arg) {
// Only include annotations for methods declared in
// classes not reachable from ancestor.
if (!ancestorClasses.contains(arg.getDeclaringClass())) {
final Pair>> key = new
Pair>>( arg.getName(),
Arrays.asList( arg.getParameterTypes() ) ) ;
Map, Annotation> annos = map.computeIfAbsent(key, k -> new HashMap<>());
for (Annotation anno : arg.getDeclaredAnnotations()) {
putIfNotPresent(annos, anno.annotationType(), anno);
}
}
return true ;
}
}) ;
// Store annotation maps from map into first method found from
// ancestor.
ancestorCA.findMethods( new UnaryPredicate() {
@Override
public boolean evaluate(Method arg) {
final Pair>> key = new
Pair>>( arg.getName(),
Arrays.asList( arg.getParameterTypes() ) ) ;
Map,Annotation> annos = map.get( key ) ;
if (annos != null && !annos.isEmpty()) {
addedAnnotations.put( arg, annos ) ;
map.remove( key ) ;
}
return true ;
}
}) ;
}
/** Return a map of all annotations defined on cls and its super
* classes and interfaces in ClassAnalyzer order. Annotations nearer
* the front of the list replace those later in the list of the same type.
* @param cls Class to analyze.
* @return Map from annotation class to annotation value.
*/
public Map,Annotation> getAnnotations( final Class> cls ) {
return getAnnotations( cls, true ) ;
}
private Map,Annotation> getAnnotations( final Class> cls,
final boolean includeAddedAnnotations ) {
final Map,Annotation> result = annotationCache.get( cls ) ;
if (result == null) {
final Map,Annotation> res =
new HashMap,Annotation>() ;
final ClassAnalyzer ca = ClassAnalyzer.getClassAnalyzer(cls) ;
ca.findClasses( new UnaryPredicate>() {
@Override
public boolean evaluate(Class> arg) {
// First, put in declared annotations if not already present.
Annotation[] annots = arg.getDeclaredAnnotations() ;
for (Annotation anno : annots) {
putIfNotPresent( res, anno.annotationType(), anno ) ;
}
if (includeAddedAnnotations) {
// Then, put in added annotations if not already present.
final Map,Annotation> emap =
addedAnnotations.get( arg ) ;
if (emap != null) {
for (Map.Entry,Annotation> entry
: emap.entrySet()) {
putIfNotPresent( res, entry.getKey(),
entry.getValue()) ;
}
}
}
return true ; // evaluate everything
}
}) ;
annotationCache.put( cls, res ) ;
return res ;
}
return result ;
}
/** Return a map of all annotations defined in method and its overriden
* methods in the inheritance order of the ClassAnalyzer for the method's
* defining class.
* @param method The method to analyze
* @return A map from annotation class to annotation
*/
public Map,Annotation> getAnnotations( Method method ) {
return getAnnotations( method, true ) ;
}
private Map,Annotation> getAnnotations( final Method method,
final boolean includeAddedAnnotations ) {
final Map,Annotation> result = annotationCache.get( method ) ;
if (result == null) {
final Class> cls = method.getDeclaringClass() ;
final Map,Annotation> res =
new HashMap,Annotation>() ;
final String methodName = method.getName() ;
final Class>[] methodParamTypes = method.getParameterTypes() ;
final ClassAnalyzer ca = ClassAnalyzer.getClassAnalyzer(cls) ;
ca.findClasses( new UnaryPredicate>() {
@Override
public boolean evaluate(Class> arg) {
Method overriddenMethod = null ;
try {
overriddenMethod = arg.getDeclaredMethod(
methodName, methodParamTypes) ;
} catch (Exception exc) {
// ignore this exception: just return on null.
}
if (overriddenMethod == null) {
// Method not defined in class arg, so skip it.
return true ;
}
if (includeAddedAnnotations) {
// First, put in added annotations if not already present.
final Map,Annotation> emap =
addedAnnotations.get( overriddenMethod ) ;
if (emap != null) {
for (Map.Entry,Annotation> entry
: emap.entrySet()) {
putIfNotPresent( res, entry.getKey(),
entry.getValue()) ;
}
}
}
// Then, put in declared annotations if not already present.
final Annotation[] annots =
overriddenMethod.getDeclaredAnnotations() ;
for (Annotation anno : annots) {
putIfNotPresent( res, anno.annotationType(), anno ) ;
}
return true ; // evaluate everything
}
}) ;
annotationCache.put( method, res ) ;
return res ;
}
return result ;
}
private Map,Annotation> makeAnnoMap( Annotation[] annos ) {
Map,Annotation> result =
new HashMap,Annotation>() ;
for (Annotation anno : annos ) {
result.put( anno.annotationType(), anno ) ;
}
return result ;
}
/** Same as cons.getParameterAnnotations, with the result converted to a
* list of maps.
*
* @param method A Java Method
* @return A list of maps from annotation class to annotation value
*/
public List
© 2015 - 2024 Weber Informatics LLC | Privacy Policy