blob: c43a72a7672fd8abb9f1a59fa0e89c313a342f2f [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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.apache.ignite.internal.processors.datastructures;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.Callable;
import org.apache.ignite.IgniteCheckedException;
import org.apache.ignite.internal.util.GridBoundedLinkedHashMap;
import org.apache.ignite.internal.util.typedef.F;
import org.apache.ignite.lang.IgniteBiTuple;
/**
* Utility class for getting annotated values from classes.
* Contains local cache of annotated methods and fields by classes for best performance.
*/
public class GridCacheAnnotationHelper<A extends Annotation> {
/** Number of entries to keep in annotation cache. */
private static final int DFLT_CLASS_CACHE_SIZE = 1000;
/** Field cache. */
private final GridBoundedLinkedHashMap<Class<?>, List<Field>> fieldCache;
/** Method cache. */
private final GridBoundedLinkedHashMap<Class<?>, List<Method>> mtdCache;
/** Annotation class. */
private final Class<A> annCls;
/** Mutex. */
private final Object mux = new Object();
/**
* Constructor.
*
* @param annCls Annotation class.
*/
public GridCacheAnnotationHelper(Class<A> annCls) {
this(annCls, DFLT_CLASS_CACHE_SIZE);
}
/**
* Constructor.
*
* @param annCls Annotation class.
* @param capacity Capacity of local caches.
*/
public GridCacheAnnotationHelper(Class<A> annCls, int capacity) {
assert annCls != null : "Annotated class mustn't be null.";
assert capacity > 0 : "Capacity must be more then zero.";
this.annCls = annCls;
fieldCache = new GridBoundedLinkedHashMap<>(capacity);
mtdCache = new GridBoundedLinkedHashMap<>(capacity);
}
/**
* Returns annotated value.
*
* @param target Object to find a value in.
* @return Value of annotated field or method.
* @throws IgniteCheckedException If failed to find.
*/
public Object annotatedValue(Object target) throws IgniteCheckedException {
IgniteBiTuple<Object, Boolean> res = annotatedValue(target, new HashSet<>(), false);
assert res != null;
return res.get1();
}
/**
* Returns annotated value.
*
* @param target Object to find a value in.
* @param visited Set of visited objects to avoid cycling.
* @param annFound Flag indicating if value has already been found.
* @return Value of annotated field or method.
* @throws IgniteCheckedException If failed to find.
*/
private IgniteBiTuple<Object, Boolean> annotatedValue(Object target, Set<Object> visited, boolean annFound)
throws IgniteCheckedException {
assert target != null;
// To avoid infinite recursion.
if (visited.contains(target))
return F.t(null, annFound);
visited.add(target);
Object val = null;
for (Class<?> cls = target.getClass(); !cls.equals(Object.class); cls = cls.getSuperclass()) {
// Fields.
for (Field f : fieldsWithAnnotation(cls)) {
f.setAccessible(true);
Object fieldVal;
try {
fieldVal = f.get(target);
}
catch (IllegalAccessException e) {
throw new IgniteCheckedException("Failed to get annotated field value [cls=" + cls.getName() +
", ann=" + annCls.getSimpleName()+']', e);
}
if (needsRecursion(f)) {
if (fieldVal != null) {
// Recursion.
IgniteBiTuple<Object, Boolean> tup = annotatedValue(fieldVal, visited, annFound);
if (!annFound && tup.get2())
// Update value only if annotation was found in recursive call.
val = tup.get1();
annFound = tup.get2();
}
}
else {
if (annFound)
throw new IgniteCheckedException("Multiple annotations has been found [cls=" + cls.getName() +
", ann=" + annCls.getSimpleName() + ']');
val = fieldVal;
annFound = true;
}
}
// Methods.
for (Method m : methodsWithAnnotation(cls)) {
if (annFound)
throw new IgniteCheckedException("Multiple annotations has been found [cls=" + cls.getName() +
", ann=" + annCls.getSimpleName() + ']');
m.setAccessible(true);
try {
val = m.invoke(target);
}
catch (Exception e) {
throw new IgniteCheckedException("Failed to get annotated method value [cls=" + cls.getName() +
", ann=" + annCls.getSimpleName()+']', e);
}
annFound = true;
}
}
return F.t(val, annFound);
}
/**
* @param f Field.
* @return {@code true} if recursive inspection is required.
*/
private boolean needsRecursion(Field f) {
assert f != null;
// Need to inspect anonymous classes, callable and runnable instances.
return f.getName().startsWith("this$") || f.getName().startsWith("val$") ||
Callable.class.isAssignableFrom(f.getType()) || Runnable.class.isAssignableFrom(f.getType());
}
/**
* Gets all entries from the specified class or its super-classes that have
* been annotated with annotation provided.
*
* @param cls Class in which search for fields.
* @return Set of entries with given annotations.
*/
private Iterable<Field> fieldsWithAnnotation(Class<?> cls) {
synchronized (mux) {
List<Field> fields = fieldCache.get(cls);
if (fields == null) {
fields = new ArrayList<>();
for (Field field : cls.getDeclaredFields()) {
Annotation ann = field.getAnnotation(annCls);
if (ann != null || needsRecursion(field))
fields.add(field);
}
if (!fields.isEmpty())
fieldCache.put(cls, fields);
}
return fields;
}
}
/**
* Gets set of methods with given annotation.
*
* @param cls Class in which search for methods.
* @return Set of methods with given annotations.
*/
private Iterable<Method> methodsWithAnnotation(Class<?> cls) {
synchronized (mux) {
List<Method> mtds = mtdCache.get(cls);
if (mtds == null) {
mtds = new ArrayList<>();
for (Method mtd : cls.getDeclaredMethods()) {
Annotation ann = mtd.getAnnotation(annCls);
if (ann != null)
mtds.add(mtd);
}
if (!mtds.isEmpty())
mtdCache.put(cls, mtds);
}
return mtds;
}
}
}