/*
This file belongs to the Servoy development and deployment environment, Copyright (C) 1997-2012 Servoy BV
This program is free software; you can redistribute it and/or modify it under
the terms of the GNU Affero General Public License as published by the Free
Software Foundation; either version 3 of the License, or (at your option) any
later version.
This program is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License along
with this program; if not, see http://www.gnu.org/licenses or write to the Free
Software Foundation,Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
*/
package com.servoy.j2db.scripting.annotations;
import java.util.Collection;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import com.servoy.j2db.util.Pair;
/**
* Handles annotations form classes, caches annotations defined for the class itself and for the interfaces it implements.
*
* Annotations have to defined at element (method or field) level, or at interface or class level.
* When an annotation is defined at class or interface, all elements in that class are annotated, but not all methods from classes inheriting from that interface or class.
*
* @author rgansevles
*
*/
public class AnnotationManager<A, AC>
{
private final Map<Pair<Pair<IAnnotatedMethod<A, AC>, IAnnotatedClass<A, AC>>, AC>, Pair<Boolean, Pair<IAnnotatedElement<A, AC>, A>>> methodAnnotationCache = new ConcurrentHashMap<Pair<Pair<IAnnotatedMethod<A, AC>, IAnnotatedClass<A, AC>>, AC>, Pair<Boolean, Pair<IAnnotatedElement<A, AC>, A>>>();
private final Map<Pair<IAnnotatedField<A, AC>, AC>, Pair<Boolean, Pair<IAnnotatedElement<A, AC>, A>>> fieldAnnotationCache = new ConcurrentHashMap<Pair<IAnnotatedField<A, AC>, AC>, Pair<Boolean, Pair<IAnnotatedElement<A, AC>, A>>>();
private final Map<Pair<IAnnotatedClass<A, AC>, AC>, Pair<Boolean, Pair<IAnnotatedElement<A, AC>, A>>> classAnnotationCache = new ConcurrentHashMap<Pair<IAnnotatedClass<A, AC>, AC>, Pair<Boolean, Pair<IAnnotatedElement<A, AC>, A>>>();
public boolean isAnnotationPresent(IAnnotatedMethod<A, AC> method, IAnnotatedClass<A, AC> originalClass, AC annotationClass)
{
return method != null && getCachedAnnotation(method, originalClass, annotationClass).getLeft().booleanValue();
}
public A getAnnotation(IAnnotatedMethod<A, AC> method, IAnnotatedClass<A, AC> originalClass, AC annotationClass)
{
if (method == null) return null;
Pair<IAnnotatedElement<A, AC>, A> pair = getCachedAnnotation(method, originalClass, annotationClass).getRight();
return pair == null ? null : pair.getRight();
}
public A getAnnotation(IAnnotatedField<A, AC> field, AC annotationClass)
{
if (field == null) return null;
Pair<IAnnotatedElement<A, AC>, A> pair = getCachedAnnotation(field, annotationClass).getRight();
return pair == null ? null : pair.getRight();
}
public A getAnnotation(IAnnotatedClass<A, AC> targetClass, AC annotationClass)
{
if (targetClass == null) return null;
Pair<IAnnotatedElement<A, AC>, A> pair = getCachedAnnotation(targetClass, annotationClass).getRight();
return pair == null ? null : pair.getRight();
}
private Pair<Boolean, Pair<IAnnotatedElement<A, AC>, A>> getCachedAnnotation(IAnnotatedMethod<A, AC> method, IAnnotatedClass<A, AC> originalClass,
AC annotationClass)
{
Pair<Pair<IAnnotatedMethod<A, AC>, IAnnotatedClass<A, AC>>, AC> key = new Pair<Pair<IAnnotatedMethod<A, AC>, IAnnotatedClass<A, AC>>, AC>(
new Pair<IAnnotatedMethod<A, AC>, IAnnotatedClass<A, AC>>(method, originalClass), annotationClass);
Pair<Boolean, Pair<IAnnotatedElement<A, AC>, A>> pair = methodAnnotationCache.get(key);
if (pair == null)
{
methodAnnotationCache.put(key, pair = getAnnotationFromSuperclasses(originalClass, method, null, annotationClass));
}
return pair;
}
private Pair<Boolean, Pair<IAnnotatedElement<A, AC>, A>> getAnnotationFromSuperclasses(IAnnotatedClass<A, AC> originalClass,
IAnnotatedMethod<A, AC> method, IAnnotatedField<A, AC> field, AC annotationClass)
{
for (IAnnotatedClass<A, AC> cls = originalClass; cls != null; cls = cls.getSuperclass())
{
// check if the method is part of an interface that has the annotation
Pair<IAnnotatedElement<A, AC>, A> pair = getAnnotationFromInterfaces(cls, method, field, annotationClass);
if (pair != null)
{
return new Pair<Boolean, Pair<IAnnotatedElement<A, AC>, A>>(Boolean.TRUE, pair);
}
}
return new Pair<Boolean, Pair<IAnnotatedElement<A, AC>, A>>(Boolean.FALSE, null);
}
private Pair<IAnnotatedElement<A, AC>, A> getAnnotationFromInterfaces(IAnnotatedClass<A, AC> cls, IAnnotatedMethod<A, AC> method,
IAnnotatedField<A, AC> field, AC searchedAnnotation)
{
A annotation = null;
IAnnotatedElement<A, AC> annotatedElement = null;
if (method != null)
{
IAnnotatedMethod<A, AC> m = cls.getMethod(method.getSignature());
if (m != null)
{
annotation = m.getAnnotation(searchedAnnotation);
annotatedElement = m;
if (annotation == null)
{
// first check if the class where the method is defined has the annotation
annotation = method.getDeclaringClass().getAnnotation(searchedAnnotation);
annotatedElement = method.getDeclaringClass();
}
}
}
if (annotation == null && field != null)
{
IAnnotatedField<A, AC> f = cls.getField(field.getName());
if (f != null)
{
annotation = f.getAnnotation(searchedAnnotation);
annotatedElement = f;
// first check if the class where the field is defined has the annotation
if (annotation == null)
{
annotation = field.getDeclaringClass().getAnnotation(searchedAnnotation);
annotatedElement = field.getDeclaringClass();
}
}
}
if (annotation == null)
{
// for fields and methods only use annotations when the field or method is defined in the class
if (method == null && field == null)
{
// looking for annotation at class level
annotation = cls.getAnnotation(searchedAnnotation);
annotatedElement = cls;
if (annotation == null)
{
// search interfaces
for (IAnnotatedClass<A, AC> anInterface : cls.getInterfaces())
{
Pair<IAnnotatedElement<A, AC>, A> pair = getAnnotationFromInterfaces(anInterface, method, field, searchedAnnotation);
if (pair != null)
{
return pair;
}
}
}
}
else
{
// for methods and fields first search interfaces in class itself and superclasses
for (IAnnotatedClass<A, AC> methodClass = cls; methodClass != null; methodClass = methodClass.getSuperclass())
{
// check if the method is part of an interface that has the annotation
for (IAnnotatedClass<A, AC> anInterface : methodClass.getInterfaces())
{
Pair<IAnnotatedElement<A, AC>, A> pair = getAnnotationFromInterfaces(anInterface, method, field, searchedAnnotation);
if (pair != null)
{
return pair;
}
}
}
// looking for annotation at field or method level, use cached annotation for class
Pair<IAnnotatedElement<A, AC>, A> pair = getCachedAnnotation(cls, searchedAnnotation).getRight();
if (pair != null)
{
// check if the place where the annotation was configured the method/field is present
if (pair.getLeft() instanceof IAnnotatedClass< ? , ? >)
{
if (method != null && ((IAnnotatedClass<A, AC>)pair.getLeft()).isAssignableFrom(method.getDeclaringClass()) &&
((IAnnotatedClass<A, AC>)pair.getLeft()).getMethod(method.getSignature()) != null)
{
return pair;
}
else if (field != null && ((IAnnotatedClass<A, AC>)pair.getLeft()).isAssignableFrom(field.getDeclaringClass()) &&
((IAnnotatedClass<A, AC>)pair.getLeft()).getField(field.getName()) != null)
{
return pair;
}
// if we get here, class/interface-level annotation is not applicable to the method/field
}
}
}
}
return annotation == null ? null : new Pair<IAnnotatedElement<A, AC>, A>(annotatedElement, annotation);
}
private Pair<Boolean, Pair<IAnnotatedElement<A, AC>, A>> getCachedAnnotation(IAnnotatedClass<A, AC> targetClass, AC annotationClass)
{
Pair<IAnnotatedClass<A, AC>, AC> key = new Pair<IAnnotatedClass<A, AC>, AC>(targetClass, annotationClass);
Pair<Boolean, Pair<IAnnotatedElement<A, AC>, A>> pair = classAnnotationCache.get(key);
if (pair == null)
{
classAnnotationCache.put(key, pair = getAnnotationFromSuperclasses(targetClass, null, null, annotationClass));
}
return pair;
}
private Pair<Boolean, Pair<IAnnotatedElement<A, AC>, A>> getCachedAnnotation(IAnnotatedField<A, AC> field, AC annotationClass)
{
Pair<IAnnotatedField<A, AC>, AC> key = new Pair<IAnnotatedField<A, AC>, AC>(field, annotationClass);
Pair<Boolean, Pair<IAnnotatedElement<A, AC>, A>> pair = fieldAnnotationCache.get(key);
if (pair == null)
{
fieldAnnotationCache.put(key, pair = getAnnotationFromSuperclasses(field.getDeclaringClass(), null, field, annotationClass));
}
return pair;
}
//////////////// interfaces //////////////////////
public interface IAnnotatedElement<A, AC>
{
A getAnnotation(AC searchedAnnotation);
}
public interface IAnnotatedMethod<A, AC> extends IAnnotatedElement<A, AC>
{
Object getSignature();
IAnnotatedClass<A, AC> getDeclaringClass();
}
public interface IAnnotatedClass<A, AC> extends IAnnotatedElement<A, AC>
{
IAnnotatedClass<A, AC> getSuperclass();
IAnnotatedField<A, AC> getField(String name);
Collection<IAnnotatedClass<A, AC>> getInterfaces();
boolean isAssignableFrom(IAnnotatedClass<A, AC> declaringClass);
IAnnotatedMethod<A, AC> getMethod(Object signature);
}
public interface IAnnotatedField<A, AC> extends IAnnotatedElement<A, AC>
{
String getName();
IAnnotatedClass<A, AC> getDeclaringClass();
}
}