All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.mikerusoft.jsonable.refelection.ReflectionCache Maven / Gradle / Ivy

There is a newer version: 1.2.13
Show newest version
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) 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 (v == null) {
                c.add(null);
            } else if (listType != null && !listType.isAssignableFrom(v.getClass())) {
                if (isPrimitiveLike(listType) && isPrimitiveLike(v.getClass())) {
                    c.add(getPrimitive(listType, v));
                } else {
                    c.add(null);
                }

            } else if (listType == null || listType.isAssignableFrom(v.getClass())) {
                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;
        boolean hasJsonAnot = expected.getAnnotation(JsonClass.class) != null;
        if (hasJsonAnot && expected.isAssignableFrom(data.getClass())) {
            return expected.cast(data);
        }
        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)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
                try {
                    return expected.cast(data);
                } catch (Exception e) {
                    throw new RuntimeException(String.valueOf(expected.getName()) + "  " + String.valueOf(data), e);
                }
        }
        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