| /* |
| * 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; |
| } |
| } |
| } |