package com.cedarsoftware.util;
import java.io.ByteArrayInputStream;
import java.io.DataInputStream;
import java.io.InputStream;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
/**
* @author John DeRegnaucourt (john@cedarsoftware.com)
* <br>
* Copyright (c) Cedar Software LLC
* <br><br>
* 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
* <br><br>
* http://www.apache.org/licenses/LICENSE-2.0
* <br><br>
* 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.
*/
public final class ReflectionUtils
{
private static final Map<Class, Collection<Field>> _reflectedFields = new ConcurrentHashMap<>();
private ReflectionUtils()
{
super();
}
/**
* Determine if the passed in class (classToCheck) has the annotation (annoClass) on itself,
* any of its super classes, any of it's interfaces, or any of it's super interfaces.
* This is a exhaustive check throughout the complete inheritance hierarchy.
* @return the Annotation if found, null otherwise.
*/
public static Annotation getClassAnnotation(final Class classToCheck, final Class annoClass)
{
final Set<Class> visited = new HashSet<>();
final LinkedList<Class> stack = new LinkedList<>();
stack.add(classToCheck);
while (!stack.isEmpty())
{
Class classToChk = stack.pop();
if (classToChk == null || visited.contains(classToChk))
{
continue;
}
visited.add(classToChk);
Annotation a = classToChk.getAnnotation(annoClass);
if (a != null)
{
return a;
}
stack.push(classToChk.getSuperclass());
addInterfaces(classToChk, stack);
}
return null;
}
private static void addInterfaces(final Class classToCheck, final LinkedList<Class> stack)
{
for (Class interFace : classToCheck.getInterfaces())
{
stack.push(interFace);
}
}
public static Annotation getMethodAnnotation(final Method method, final Class annoClass)
{
final Set<Class> visited = new HashSet<>();
final LinkedList<Class> stack = new LinkedList<>();
stack.add(method.getDeclaringClass());
while (!stack.isEmpty())
{
Class classToChk = stack.pop();
if (classToChk == null || visited.contains(classToChk))
{
continue;
}
visited.add(classToChk);
Method m = getMethod(classToChk, method.getName(), method.getParameterTypes());
if (m == null)
{
continue;
}
Annotation a = m.getAnnotation(annoClass);
if (a != null)
{
return a;
}
stack.push(classToChk.getSuperclass());
addInterfaces(method.getDeclaringClass(), stack);
}
return null;
}
public static Method getMethod(Class c, String method, Class...types) {
try {
return c.getMethod(method, types);
} catch (Exception nse) {
return null;
}
}
/**
* Get all non static, non transient, fields of the passed in class, including
* private fields. Note, the special this$ field is also not returned. The result
* is cached in a static ConcurrentHashMap to benefit execution performance.
* @param c Class instance
* @return Collection of only the fields in the passed in class
* that would need further processing (reference fields). This
* makes field traversal on a class faster as it does not need to
* continually process known fields like primitives.
*/
public static Collection<Field> getDeepDeclaredFields(Class c)
{
if (_reflectedFields.containsKey(c))
{
return _reflectedFields.get(c);
}
Collection<Field> fields = new ArrayList<>();
Class curr = c;
while (curr != null)
{
getDeclaredFields(curr, fields);
curr = curr.getSuperclass();
}
_reflectedFields.put(c, fields);
return fields;
}
/**
* Get all non static, non transient, fields of the passed in class, including
* private fields. Note, the special this$ field is also not returned. The
* resulting fields are stored in a Collection.
* @param c Class instance
* that would need further processing (reference fields). This
* makes field traversal on a class faster as it does not need to
* continually process known fields like primitives.
*/
public static void getDeclaredFields(Class c, Collection<Field> fields) {
try
{
Field[] local = c.getDeclaredFields();
for (Field field : local)
{
if (!field.isAccessible())
{
try
{
field.setAccessible(true);
}
catch (Exception ignored) { }
}
int modifiers = field.getModifiers();
if (!Modifier.isStatic(modifiers) &&
!field.getName().startsWith("this$") &&
!Modifier.isTransient(modifiers))
{ // speed up: do not count static fields, do not go back up to enclosing object in nested case, do not consider transients
fields.add(field);
}
}
}
catch (Throwable ignored)
{
ExceptionUtilities.safelyIgnoreException(ignored);
}
}
/**
* Return all Fields from a class (including inherited), mapped by
* String field name to java.lang.reflect.Field.
* @param c Class whose fields are being fetched.
* @return Map of all fields on the Class, keyed by String field
* name to java.lang.reflect.Field.
*/
public static Map<String, Field> getDeepDeclaredFieldMap(Class c)
{
Map<String, Field> fieldMap = new HashMap<>();
Collection<Field> fields = getDeepDeclaredFields(c);
for (Field field : fields)
{
String fieldName = field.getName();
if (fieldMap.containsKey(fieldName))
{ // Can happen when parent and child class both have private field with same name
fieldMap.put(field.getDeclaringClass().getName() + '.' + fieldName, field);
}
else
{
fieldMap.put(fieldName, field);
}
}
return fieldMap;
}
/**
* Return the name of the class on the object, or "null" if the object is null.
* @param o Object to get the class name.
* @return String name of the class or "null"
*/
public static String getClassName(Object o)
{
return o == null ? "null" : o.getClass().getName();
}
public static String getClassNameFromByteCode(byte[] byteCode) throws Exception
{
InputStream is = new ByteArrayInputStream(byteCode);
DataInputStream dis = new DataInputStream(is);
dis.readLong(); // skip header and class version
int cpcnt = (dis.readShort() & 0xffff) - 1;
int[] classes = new int[cpcnt];
String[] strings = new String[cpcnt];
for (int i=0; i < cpcnt; i++)
{
int t = dis.read();
if (t == 7)
{
classes[i] = dis.readShort() & 0xffff;
}
else if (t == 1)
{
strings[i] = dis.readUTF();
}
else if (t == 5 || t == 6)
{
dis.readLong();
i++;
}
else if (t == 8)
{
dis.readShort();
}
else
{
dis.readInt();
}
}
dis.readShort(); // skip access flags
return strings[classes[(dis.readShort() & 0xffff) - 1] - 1].replace('/', '.');
}
}