com.mikerusoft.jsonable.refelection.ReflectionCache Maven / Gradle / Ivy
package com.mikerusoft.jsonable.refelection;
import com.mikerusoft.jsonable.adapters.MethodWrapper;
import com.mikerusoft.jsonable.adapters.ParserAdapter;
import com.mikerusoft.jsonable.annotations.CustomField;
import com.mikerusoft.jsonable.annotations.DateField;
import com.mikerusoft.jsonable.annotations.JsonClass;
import com.mikerusoft.jsonable.annotations.JsonField;
import com.mikerusoft.jsonable.transform.DateTransformer;
import com.mikerusoft.jsonable.transform.JsonParser;
import com.mikerusoft.jsonable.utils.ConfInfo;
import org.apache.commons.lang3.EnumUtils;
import org.apache.commons.lang3.StringEscapeUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import java.io.File;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.lang.reflect.*;
import java.math.BigDecimal;
import java.net.JarURLConnection;
import java.net.URL;
import java.net.URLConnection;
import java.net.URLDecoder;
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
/**
* @author Grinfeld Mikhail
* @since 12/6/2014.
*/
public class ReflectionCache {
private static Log log = LogFactory.getLog(ReflectionCache.class);
public static ReflectionCache instance;
private static final Object lock = new Object();
public static ReflectionCache get() {
if (instance == null) {
synchronized (lock) {
if (instance == null)
instance = new ReflectionCache();
}
}
return instance;
}
Map> classes;
Map, Set> invokers;
private ReflectionCache() {
classes = new ConcurrentHashMap<>();
invokers = new ConcurrentHashMap<>();
}
/**
* Private helper method.
*
* @param connection the connection to the jar
* @param pckgname the package name to search for
* @param classes the current ArrayList of all classes. This method will simply add new classes.
* @param _interface interface to bring its sub classes
* @throws ClassNotFoundException if a file isn't loaded but still is in the jar file
* @throws IOException if it can't correctly read from the jar file.
*/
public static void checkJarFile(JarURLConnection connection, String pckgname, List> classes, Class _interface)
throws ClassNotFoundException, IOException {
final JarFile jarFile = connection.getJarFile();
final Enumeration entries = jarFile.entries();
String name;
for (JarEntry jarEntry = null; entries.hasMoreElements() && ((jarEntry = entries.nextElement()) != null);) {
name = jarEntry.getName();
if (name.contains(".class")) {
name = name.substring(0, name.length() - 6).replace('/', '.');
if (name.contains(pckgname)) {
Class> clazz = Class.forName(name);
if (_interface.isAssignableFrom(clazz) && !clazz.isInterface())
classes.add( (Class)clazz );
}
}
}
}
/**
* Private helper method
*
* @param directory The directory to start with
* @param pckgname The package name to search for. Will be needed for getting the Class object.
* @param classes if a file isn't loaded but still is in the directory
* @throws ClassNotFoundException
*/
public static void checkDirectory(File directory, String pckgname, List> classes, Class _interface) throws ClassNotFoundException {
File tmpDirectory;
if (directory.exists() && directory.isDirectory()) {
final String[] files = directory.list();
for (final String file : files) {
if (file.endsWith(".class")) {
try {
Class> clazz = Class.forName(pckgname + '.' + file.substring(0, file.length() - 6));
if (_interface.isAssignableFrom(clazz) && !clazz.isInterface())
classes.add( (Class)clazz );
} catch (final NoClassDefFoundError e) {
// do nothing. this class hasn't been found by the
// loader, and we don't care.
}
} else if ((tmpDirectory = new File(directory, file)).isDirectory()) {
checkDirectory(tmpDirectory, pckgname + "." + file, classes, _interface);
}
}
}
}
/**
* Attempts to list all the classes in the specified package as determined
* by the context class loader
*
* @param pckgname the package name to search
* @param _interface searching criteria, i.e. classes implement for _interface
* @param type of implemented interface
* @return a list of classes that exist within that package
* @throws ClassNotFoundException if something went wrong
*/
public static List> getClassesForPackage(String pckgname, Class _interface)
throws ClassNotFoundException {
final List> classes = new ArrayList>();
try {
final ClassLoader cld = Thread.currentThread().getContextClassLoader();
if (cld == null)
throw new ClassNotFoundException("Can't get class loader.");
final Enumeration resources = cld.getResources(pckgname.replace('.', '/'));
URLConnection connection;
for (URL url = null; resources.hasMoreElements() && ((url = resources.nextElement()) != null);) {
try {
connection = url.openConnection();
if (connection instanceof JarURLConnection) {
checkJarFile((JarURLConnection) connection, pckgname, classes, _interface);
} else {
try {
checkDirectory(new File(URLDecoder.decode(url.getPath(), "UTF-8")), pckgname, classes, _interface);
} catch (final UnsupportedEncodingException ex) {
throw new ClassNotFoundException(pckgname + " does not appear to be a valid package (Unsupported encoding)", ex);
}
}
} catch (final IOException ioex) {
throw new ClassNotFoundException("IOException was thrown when trying to get all resources for " + pckgname, ioex);
}
}
} catch (final NullPointerException ex) {
throw new ClassNotFoundException(pckgname + " does not appear to be a valid package (Null pointer exception)", ex);
} catch (final IOException ioex) {
throw new ClassNotFoundException("IOException was thrown when trying to get all resources for " + pckgname, ioex);
}
return classes;
}
public static boolean inGroup(String[] groups, String[] allGroups) {
if (allGroups == null || allGroups.length == 0)
return true;
if (groups == null || groups.length == 0)
return true;
return new ArrayList<>(Arrays.asList(groups)).removeAll(Arrays.asList(allGroups));
}
public static boolean inGroup(String[] groups, List allGroups) {
if (allGroups == null || allGroups.size() == 0)
return true;
if (groups == null || groups.length == 0)
return true;
return new ArrayList<>(Arrays.asList(groups)).removeAll(allGroups);
}
public static void createSpecific(Map possible, Object o, Class> clazz, List groups) throws IllegalAccessException, InvocationTargetException, InstantiationException {
Collection invokers = get().getInvokers(clazz);
for (Invoker i : invokers) {
if (i.setEnabled()) {
if (inGroup(i.getSetterGroups(), groups))
i.set(o, possible.get(i.getSetterName()));
}
}
}
public static Object createClass(Map possible, List groups) {
String cl = ConfInfo.getClassProperty();
String className = (String)possible.get(cl);
if (StringUtils.isEmpty(className))
return possible;
Class> clazz = null;
try {
// using cache in order to avoid searching class in ClassLoader, but get it directly from cache.
// There is pitfall: cache could be large same as ClassLoader, if most classes will be serialized
clazz = get().getClass(className);
} catch (ClassNotFoundException e) {
return possible;
}
if (clazz == null)
return possible;
try {
if (isPrimitiveLike(clazz)) {
Object value = possible.get("value");
return getPrimitive(clazz, value);
}
if (clazz.isEnum()) {
return createEnum(clazz, possible);
}
Object o = ConfInfo.getFactory(clazz).newInstance(Collections.unmodifiableMap(possible));
createSpecific(possible, o, o.getClass(), groups);
return o;
} catch (IllegalAccessException | InstantiationException | InvocationTargetException e) {
log.debug("Failed to create class " + className + " with error: " + e.getMessage());
}
return possible;
}
public static Object guessClass(Object possible, Class> clazz) throws ClassNotFoundException, NoSuchFieldException, InstantiationException, IllegalAccessException {
if (clazz == null)
return possible;
if (possible == null)
return null;
if (isPrimitiveLike(clazz)) {
if (clazz.isAssignableFrom(possible.getClass()))
return possible;
return getPrimitive(clazz, possible);
} else if (Map.class.isAssignableFrom(clazz)) {
Object actual = ConfInfo.getFactory(clazz).newInstance(Collections.unmodifiableMap((Map)possible));
Class> actualClass = actual.getClass();
Collection invokers = ReflectionCache.get().getInvokers(actualClass);
// todo: add to Invoker method which return class of setter and getter
// todo: check if it's the same class as expected in Map. If yes, call setter, else call itself again
for (Object entry : ((Map)possible).entrySet()) {
Map.Entry e = (Map.Entry)entry;
}
Method[] methods = get().getClass(clazz.getName()).getMethods();
} else if (clazz.isArray()) {
Class> compType = clazz.getComponentType();
if (compType != null && !compType.isPrimitive() && !compType.equals(String.class)) {
// todo: fill data
}
}
return possible;
}
public void clear() {
classes.clear();
invokers.clear();
}
public Collection getInvokers(Class> clazz) {
if (clazz == null || Object.class.equals(clazz))
return new ArrayList<>();
Set invokers = this.invokers.get(clazz);
if (invokers != null)
return Collections.unmodifiableCollection(invokers);
Class> inherit = clazz;
invokers = new HashSet<>();
do {
ParserAdapter> adapter = ConfInfo.getAdapter(inherit);
if (inherit.getAnnotation(JsonClass.class) != null || adapter != null) {
Field[] fields = inherit.getDeclaredFields();
Method[] methods = inherit.getDeclaredMethods();
for (Field f : fields) {
if (f.getAnnotation(JsonField.class) != null) {
String name = f.getAnnotation(JsonField.class).name();
if ("".equals(name.trim()))
name = f.getName();
invokers.add(new FieldInvoker(name, f));
}
}
if (adapter != null) {
for (MethodWrapper mw : adapter.getParams()) {
invokers.add(new MethodInvoker(mw.getName(), mw.getSetter() ,mw.getName(), mw.getGetter()));
}
}
for (Method m : methods) {
if (m.getAnnotation(CustomField.class) != null) {
String name = m.getAnnotation(CustomField.class).name();
if ("".equals(name.trim()))
name = m.getName();
Method setter = m.getParameterTypes().length == 1 ? m : null;
Method getter = m.getParameterTypes().length == 0 ? m : null;
String setterName = m.getParameterTypes().length == 1 ? name : null;
String getterName = m.getParameterTypes().length == 0 ? name : null;
invokers.add(new MethodInvoker(setterName, setter, getterName, getter));
}
}
}
inherit = inherit.getSuperclass();
} while (inherit != null && !Object.class.equals(inherit));
putClass(clazz);
this.invokers.put(clazz, invokers);
return Collections.unmodifiableCollection(invokers);
}
protected void putClass(Class> clazz) { classes.put(clazz.getName(), clazz); }
public Class> getClass(String className) throws ClassNotFoundException {
Class> clazz = classes.get(className);
if (clazz == null) {
clazz = Class.forName(className);
putClass(clazz);
}
return clazz;
}
public static Object getPrimitive(Class> clazz, Object value) throws InstantiationException {
if (value == null)
return null;
if (Byte.TYPE.equals(clazz) || Byte.class.equals(clazz)) {
Byte b = Byte.valueOf(String.valueOf(value));
if (b != null && Byte.TYPE.equals(clazz))
return b.byteValue();
return b;
} else if (Boolean.TYPE.equals(clazz) || Boolean.class.equals(clazz)) {
Boolean s = Boolean.valueOf(String.valueOf(value));
if (s != null && Byte.TYPE.equals(clazz))
return s.booleanValue();
return s;
} else if (Short.TYPE.equals(clazz) || Short.class.equals(clazz)) {
Short s = Short.valueOf(String.valueOf(value));
if (s != null && Byte.TYPE.equals(clazz))
return s.shortValue();
return s;
} else if (Character.TYPE.equals(clazz) || Character.class.equals(clazz)) {
Character c = String.valueOf(value).charAt(0);
if (c != null && Byte.TYPE.equals(clazz))
return c.charValue();
return c;
} else if (Integer.TYPE.equals(clazz) || Integer.class.equals(clazz)) {
Integer i = Integer.valueOf(String.valueOf(value));
if (i != null && Byte.TYPE.equals(clazz))
return i.intValue();
return i;
} else if (Long.TYPE.equals(clazz) || Long.class.equals(clazz)) {
Long l = Long.valueOf(String.valueOf(value));
if (l != null && Byte.TYPE.equals(clazz))
return l.longValue();
return l;
} else if (Float.TYPE.equals(clazz) || Float.class.equals(clazz)) {
Float f = Float.valueOf(String.valueOf(value));
if (f != null && Byte.TYPE.equals(clazz))
return f.floatValue();
return f;
} else if (Double.TYPE.equals(clazz) || Double.class.equals(clazz)) {
Double d = Double.valueOf(String.valueOf(value));
if (d != null && Byte.TYPE.equals(clazz))
return d.doubleValue();
return d;
} else if (BigDecimal.class.equals(clazz)) {
return BigDecimal.valueOf(Double.valueOf(String.valueOf(value)));
}
throw new InstantiationException("Invalid type" + clazz + " for value " + value); // should never occur
}
public static boolean isPrimitiveLike(Class> clazz) {
return clazz.isPrimitive() || Boolean.class.equals(clazz) || Byte.class.equals(clazz) ||
Short.class.equals(clazz) || Character.class.equals(clazz) ||
Integer.class.equals(clazz) || Long.class.equals(clazz) ||
Double.class.equals(clazz) || Float.class.equals(clazz) || clazz.equals(BigDecimal.class);
}
public static Object createEnum(Class> clazz, Map possible) {
String enumName = (String)possible.get("name");
return EnumUtils.getEnum((Class extends Enum>) clazz, enumName);
}
public static Object getValue(Class> expected, Class>[] generic, Object data, DateField annoted) throws InstantiationException {
int dateType = -1;
String format = "";
if (annoted != null) {
dateType = annoted.type();
format = annoted.format();
}
return getValue(expected, generic, data, dateType, format);
}
private static Collection createCollection(Class clazz) {
return createList(clazz);
}
private static Set createSet(Class clazz) {
return new HashSet();
}
private static Map createMap(Class clazz1, Class clazz2) {
return new HashMap();
}
private static List createList(Class clazz) {
return new ArrayList();
}
public static Object getValue(Class> expected, Class>[] generic, Object data) throws InstantiationException {
return getValue(expected, generic, data, -1, "");
}
public static Object getCollectionValueFromList(Class> expected, Class>[] generic, Object data) throws InstantiationException {
Collection c = null;
if ((Collection.class.isAssignableFrom(expected) || List.class.isAssignableFrom(expected))) {
c = createList(data.getClass());
} else if (Set.class.isAssignableFrom(expected)) {
c = createSet(data.getClass());
}
Class> listType = null;
if (generic != null && generic.length > 0)
listType = generic[0];
for (Object v : (Iterable) data) {
if (listType == null)
c.add(v);
else
c.add(getValue(listType, null, v));
}
return c;
}
public static Object getValue(Class> expected, Class>[] generic, Object data, int dateTimeType, String format) throws InstantiationException {
if (data == null)
return null;
if (expected.equals(Boolean.TYPE) || expected.equals(Boolean.class)) {
if (data instanceof String) {
String value = (String) data;
return !("".equals(value) || "0".equals(value) || "false".equalsIgnoreCase(value));
} else {
return data;
}
} else if (expected.isPrimitive()) {
return getPrimitive(expected, data);
} else if (Date.class.isAssignableFrom(expected) && (dateTimeType == DateTransformer.TIMESTAMP_TYPE || dateTimeType == DateTransformer.STRING_TYPE)) {
switch (dateTimeType) {
case DateTransformer.TIMESTAMP_TYPE:
if (Date.class.isAssignableFrom(data.getClass())) {
return ((Date)data).getTime(); // means we write out the date
} else {
return new Date((Long) data); // means we read in the date
}
case DateTransformer.STRING_TYPE:
try {
DateFormat dt = new SimpleDateFormat(format);
if (Date.class.isAssignableFrom(data.getClass())) {
return dt.format((Date)data); // means we write out the date
} else {
return dt.parse((String) data); // means we read in the date
}
} catch (ParseException e) {
throw new IllegalArgumentException("Incompatible types for " + expected.getName(), e);
}
}
} else if (expected.equals(String.class)) {
return StringEscapeUtils.unescapeJson((String)data);
} else if (expected.isArray() && data instanceof Collection) {
Collection> l = ((Collection) data);
Object ar = Array.newInstance(expected.getComponentType(), l.size());
System.arraycopy(l.toArray(), 0, ar, 0, l.size());
return ar;
} else if (Collection.class.isAssignableFrom(expected) && data.getClass().isArray()) {
return getCollectionValueFromList(expected, generic, data);
} else if (Collection.class.isAssignableFrom(expected) && data instanceof Collection) {
return getCollectionValueFromList(expected, generic, data);
} else if (expected.isEnum()) {
if (data.getClass().isEnum())
return data;
else if (data instanceof String) {
String s = (String)data;
if (!StringUtils.isEmpty(s)) {
return Enum.valueOf((Class extends Enum>)expected, s);
}
return null;
}
} else {
if (data.getClass().equals(Long.class)) {
Long val = (Long) data;
if (expected.equals(Byte.class)) {
return val.byteValue();
} else if (expected.equals(Short.class)) {
return val.shortValue();
} else if (expected.equals(Integer.class)) {
return val.intValue();
} else if (expected.equals(Long.class)) {
return val;
}
} else if (data.getClass().equals(Double.class)) {
Double val = (Double) data;
if (expected.equals(Float.class)) {
return val.floatValue();
} else if (expected.equals(Double.class)) {
return val;
}
} else
return expected.cast(data);
}
return data;
}
public static void fill(Method m, Class[] generics, Object owner, Object data) throws IllegalArgumentException, IllegalAccessException, InstantiationException, InvocationTargetException {
if (data == null || data instanceof String && "".equals(data)) {
return;
}
int dateType = -1;
String format = "";
if (m.isAnnotationPresent(DateField.class)) {
dateType = m.getAnnotation(DateField.class).type();
format = m.getAnnotation(DateField.class).format();
}
Object value = getValue(m.getParameterTypes()[0], generics, data, dateType, format);
if (owner == null)
throw new IllegalArgumentException("Trying to invoke setter " + m.getName() + " for value " + String.valueOf(value) + " for Object which is null");
m.setAccessible(true);
m.invoke(owner, value);
}
public static void fill(Field f, Class[] generics, Object owner, Object data) throws IllegalArgumentException, IllegalAccessException, InstantiationException {
if (data == null || data instanceof String && "".equals(data)) {
return;
}
int dateType = -1;
String format = "";
if (f.isAnnotationPresent(DateField.class)) {
dateType = f.getAnnotation(DateField.class).type();
format = f.getAnnotation(DateField.class).format();
}
if (f.getGenericType() != null && f.getGenericType() instanceof ParameterizedType) {
Type[] types = ((ParameterizedType)f.getGenericType()).getActualTypeArguments();
if (types != null && types.length > 0) {
generics = new Class>[types.length];
for (int i=0; i)types[i];
}
}
}
Object value = getValue(f.getType(), generics, data, dateType, format);
if (owner == null)
throw new IllegalArgumentException("Trying to set field " + f.getName() + " for value " + String.valueOf(value) + " for Object which is null");
f.setAccessible(true);
f.set(owner, value);
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy