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

play.utils.Java Maven / Gradle / Ivy

There is a newer version: 1.5.0
Show newest version
package play.utils;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
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.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.FutureTask;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.bytecode.SourceFileAttribute;
import play.Play;
import play.classloading.ApplicationClassloaderState;
import play.classloading.enhancers.LocalvariablesNamesEnhancer.LocalVariablesNamesTracer;
import play.data.binding.Binder;
import play.data.binding.ParamNode;
import play.data.binding.RootParamNode;
import play.exceptions.UnexpectedException;
import play.mvc.After;
import play.mvc.Before;
import play.mvc.Finally;
import play.mvc.With;

/**
 * Java utils
 */
public class Java {

    protected static JavaWithCaching _javaWithCaching = new JavaWithCaching();
    protected static ApplicationClassloaderState _lastKnownApplicationClassloaderState = Play.classloader.currentState;
    protected static Object _javaWithCachingLock = new Object();

    protected static JavaWithCaching getJavaWithCaching() {
        synchronized( _javaWithCachingLock ) {
            // has the state of the ApplicationClassloader changed?
            ApplicationClassloaderState currentApplicationClasloaderState = Play.classloader.currentState;
            if( !currentApplicationClasloaderState.equals( _lastKnownApplicationClassloaderState )) {
                // it has changed.
                // we must drop our current _javaWithCaching and create a new one...
                // and start the caching over again.
                _lastKnownApplicationClassloaderState = currentApplicationClasloaderState;
                _javaWithCaching = new JavaWithCaching();

            }
            return _javaWithCaching;
        }
    }


    public static String[] extractInfosFromByteCode(byte[] code) {
        try {
            CtClass ctClass = ClassPool.getDefault().makeClass(new ByteArrayInputStream(code));
            String sourceName = ((SourceFileAttribute) ctClass.getClassFile().getAttribute("SourceFile")).getFileName();
            return new String[]{ctClass.getName(), sourceName};
        } catch (Exception e) {
            throw new UnexpectedException("Cannot read a scala generated class using javassist", e);
        }
    }

    /**
     * Try to discover what is hidden under a FutureTask (hack)
     */
    public static Object extractUnderlyingCallable(FutureTask futureTask) {
        try {
            Object callable = null;
            // Try to search for the Filed sync first, if not present will try filed callable
            try{
                Field syncField = FutureTask.class.getDeclaredField("sync");
                syncField.setAccessible(true);
                Object sync = syncField.get(futureTask);
                if (sync != null) {
                    Field callableField = sync.getClass().getDeclaredField("callable");
                    callableField.setAccessible(true);
                    callable = callableField.get(sync);
                }
            } catch(NoSuchFieldException ex) {
               Field callableField = FutureTask.class.getDeclaredField("callable");
               callableField.setAccessible(true);
               callable = callableField.get(futureTask);
            }
            if (callable != null && callable.getClass().getSimpleName().equals("RunnableAdapter")) {
                Field taskField = callable.getClass().getDeclaredField("task");
                taskField.setAccessible(true);
                return taskField.get(callable);
            }
            return callable;
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * Find the first public static method of a controller class
     * @param name The method name
     * @param clazz The class
     * @return The method or null
     */
    public static Method findActionMethod(String name, Class clazz) {
	    // We don't want to check the views
	  	while (!clazz.getName().equals("java.lang.Object")) {
		    for (Method m : clazz.getDeclaredMethods()) {
			    if (m.getName().equalsIgnoreCase(name) && Modifier.isPublic(m.getModifiers())) {
                    // Check that it is not an intercepter
                    if (!m.isAnnotationPresent(Before.class) && !m.isAnnotationPresent(After.class) && !m.isAnnotationPresent(Finally.class)) {
                        return m;
                    }
                }
            }
		    clazz = clazz.getSuperclass();
        }
        return null;
    }


    /**
     * Invoke a static method
     * @param clazz The class
     * @param method The method name
     * @return The result
     * @throws java.lang.Exception
     */
    public static Object invokeStatic(Class clazz, String method) throws Exception {
        return invokeStatic(clazz, method, new Object[0]);
    }

    public static Object invokeStatic(String clazz, String method) throws Exception {
        return invokeStatic(Play.classloader.loadClass(clazz), method, new Object[0]);
    }

    /**
     * Invoke a static method with args
     * @param clazz The class
     * @param method The method name
     * @param args Arguments
     * @return The result
     * @throws java.lang.Exception
     */
    public static Object invokeStatic(Class clazz, String method, Object... args) throws Exception {
        Class[] types = new Class[args.length];
        for (int i = 0; i < args.length; i++) {
            types[i] = args[i].getClass();
        }
        Method m = clazz.getDeclaredMethod(method, types);
        m.setAccessible(true);
        return m.invoke(null, args);
    }

    public static Object invokeStaticOrParent(Class clazz, String method, Object... args) throws Exception {
        Class[] types = new Class[args.length];
        for (int i = 0; i < args.length; i++) {
            types[i] = args[i].getClass();
        }
        Method m = null;
        while (!clazz.equals(Object.class) && m == null) {
            try {
                m = clazz.getDeclaredMethod(method, types);
            } catch (Exception e) {
                clazz = clazz.getSuperclass();
            }
        }
        if (m != null) {
            m.setAccessible(true);
            if (Modifier.isStatic(m.getModifiers())) {
                return m.invoke(null, args);
            } else {
                Object instance = m.getDeclaringClass().getDeclaredField("MODULE$").get(null);
                return m.invoke(instance, args);
            }
        }
        throw new NoSuchMethodException(method);
    }

    public static Object invokeChildOrStatic(Class clazz, String method, Object... args) throws Exception {

        Class invokedClass = null;
        List assignableClasses = Play.classloader.getAssignableClasses(clazz);
        if(assignableClasses.size() == 0)
        {
            invokedClass = clazz;
        }
        else
        {
            invokedClass = assignableClasses.get(0);
        }
        
        return Java.invokeStaticOrParent(invokedClass, method, args);
    }

    public static Object invokeStatic(Method method, Map args) throws Exception {
        return method.invoke(null, prepareArgs(method, args));
    }

    public static Object invokeStatic(Method method, Object[] args) throws Exception {
        return method.invoke(null, args);
    }

    static Object[] prepareArgs(Method method, Map args) throws Exception {
        String[] paramsNames = parameterNames(method);
        if (paramsNames == null && method.getParameterTypes().length > 0) {
            throw new UnexpectedException("Parameter names not found for method " + method);
        }

        RootParamNode rootParamNode = ParamNode.convert(args);

        Object[] rArgs = new Object[method.getParameterTypes().length];
        for (int i = 0; i < method.getParameterTypes().length; i++) {
            rArgs[i] = Binder.bind(rootParamNode, paramsNames[i], method.getParameterTypes()[i], method.getGenericParameterTypes()[i], method.getParameterAnnotations()[i]);
        }
        return rArgs;
    }

    /**
     * Retrieve parameter names of a method
     */
    public static String[] parameterNames(Method method) throws Exception {
        try {
            return (String[]) method.getDeclaringClass().getDeclaredField("$" + method.getName() + LocalVariablesNamesTracer.computeMethodHash(method.getParameterTypes())).get(null);
        } catch (Exception e) {
            throw new UnexpectedException("Cannot read parameter names for " + method);
        }
    }

    public static String rawMethodSignature(Method method) {
        StringBuilder sig = new StringBuilder();
        sig.append(method.getDeclaringClass().getName());
        sig.append(".");
        sig.append(method.getName());
        sig.append('(');
        for (Class clazz : method.getParameterTypes()) {
            sig.append(rawJavaType(clazz));
        }
        sig.append(")");
        sig.append(rawJavaType(method.getReturnType()));
        return sig.toString();
    }

    public static String rawJavaType(Class clazz) {
        if (clazz.getName().equals("void")) {
            return "V";
        }
        if (clazz.getName().equals("boolean")) {
            return "Z";
        }
        if (clazz.getName().equals("byte")) {
            return "B";
        }
        if (clazz.getName().equals("char")) {
            return "C";
        }
        if (clazz.getName().equals("double")) {
            return "D";
        }
        if (clazz.getName().equals("float")) {
            return "F";
        }
        if (clazz.getName().equals("int")) {
            return "I";
        }
        if (clazz.getName().equals("long")) {
            return "J";
        }
        if (clazz.getName().equals("short")) {
            return "S";
        }
        if (clazz.getName().startsWith("[")) {
            return clazz.getName().replace('.', '/');
        }
        return "L" + (clazz.getName().replace('.', '/')) + ";";
    }

    /**
     * Find all annotated method from a class
     * @param clazz The class
     * @param annotationType The annotation class
     * @return A list of method object
     */
    public static List findAllAnnotatedMethods(Class clazz, Class annotationType) {

        return getJavaWithCaching().findAllAnnotatedMethods(clazz, annotationType);
    }

    /**
     * Find all annotated method from a class
     * @param classes The classes
     * @param annotationType The annotation class
     * @return A list of method object
     */
    public static List findAllAnnotatedMethods(List classes, Class annotationType) {
        List methods = new ArrayList();
        for (Class clazz : classes) {
            methods.addAll(findAllAnnotatedMethods(clazz, annotationType));
        }
        return methods;
    }

    public static void findAllFields(Class clazz, Set found) {
        Field[] fields = clazz.getDeclaredFields();
        for (int i = 0; i < fields.length; i++) {
            found.add(fields[i]);
        }
        Class sClazz = clazz.getSuperclass();
        if (sClazz != null && sClazz != Object.class) {
            findAllFields(sClazz, found);
        }
    }
    /** cache */
    private static Map wrappers = new HashMap();

    public static FieldWrapper getFieldWrapper(Field field) {
        if (wrappers.get(field) == null) {
            FieldWrapper fw = new FieldWrapper(field);
            if (play.Logger.isTraceEnabled()) {
                play.Logger.trace("caching %s", fw);
            }
            wrappers.put(field, fw);
        }
        return wrappers.get(field);
    }

    public static byte[] serialize(Object o) throws Exception {
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        ObjectOutputStream oo = new ObjectOutputStream(baos);
        oo.writeObject(o);
        oo.flush();
        oo.close();
        return baos.toByteArray();
    }

    public static Object deserialize(byte[] b) throws Exception {
        ByteArrayInputStream bais = new ByteArrayInputStream(b);
        ObjectInputStream oi = new ObjectInputStream(bais);
        return oi.readObject();
    }

    /**
     * Field accessor
     * set and get value for a property, using the getter/setter when it exists or direct access otherwise.
     * final, native or static properties are safely ignored
     */
    public static class FieldWrapper {

        final static int unwritableModifiers = Modifier.FINAL | Modifier.NATIVE | Modifier.STATIC;
        private Method setter;
        private Method getter;
        private Field field;
        private boolean writable;
        private boolean accessible;

        private FieldWrapper(Method setter, Method getter) {
            this.setter = setter;
            this.getter = getter;
        }

        private FieldWrapper(Field field) {
            this.field = field;
            accessible = field.isAccessible();
            writable = ((field.getModifiers() & unwritableModifiers) == 0);
            String property = field.getName();
            try {
                String setterMethod = "set" + property.substring(0, 1).toUpperCase() + property.substring(1);
                setter = field.getDeclaringClass().getMethod(setterMethod, field.getType());
            } catch (Exception ex) {
            }
            try {
                String getterMethod = "get" + property.substring(0, 1).toUpperCase() + property.substring(1);
                getter = field.getDeclaringClass().getMethod(getterMethod);
            } catch (Exception ex) {
            }
        }

        public boolean isModifiable() {
            return writable;
        }

        public void setValue(Object instance, Object value) {
            if (!writable) {
                return;
            }
            try {
                if (setter != null) {
                    if (play.Logger.isTraceEnabled()) {
                        play.Logger.trace("invoke setter %s on %s with value %s", setter, instance, value);
                    }
                    setter.invoke(instance, value);
                } else {
                    if (!accessible) {
                        field.setAccessible(true);
                    }
                    if (play.Logger.isTraceEnabled()) {
                        play.Logger.trace("field.set(%s, %s)", instance, value);
                    }
                    field.set(instance, value);
                    if (!accessible) {
                        field.setAccessible(accessible);
                    }
                }
            } catch (Exception ex) {
                play.Logger.info("ERROR: when setting value for field %s - %s", field, ex);
            }
        }

        public Object getValue(Object instance) {
            try {
                if (getter != null) {
                    return getter.invoke(instance);
                } else {
                    return field.get(instance);
                }
            } catch (Exception ex) {
                play.Logger.info("ERROR: when getting value for field %s - %s", field, ex);
            }
            return null;
        }

        @Override
        public String toString() {
            return "FieldWrapper (" + (writable ? "RW" : "R ") + ") for " + field;
        }
    }

}


/**
 * This is an internal class uses only by the Java-class.
 * It contains functionality with caching..
 *
 * The idea is that the Java-objects creates a new instance of JavaWithCaching,
 * each time something new is compiled..
 *
 */
class JavaWithCaching {

    /**
     * Class uses as key for storing info about the relation between a Class and an Annotation
     */
    private static class ClassAndAnnotation {
        private final Class clazz;
        private final Class annotation;

        private ClassAndAnnotation(Class clazz, Class annotation) {
            this.clazz = clazz;
            this.annotation = annotation;
        }

        @Override
        public boolean equals(Object o) {
            if (this == o) return true;
            if (o == null || getClass() != o.getClass()) return false;

            ClassAndAnnotation that = (ClassAndAnnotation) o;

            if (annotation != null ? !annotation.equals(that.annotation) : that.annotation != null) return false;
            if (clazz != null ? !clazz.equals(that.clazz) : that.clazz != null) return false;

            return true;
        }

        @Override
        public int hashCode() {
            int result = clazz != null ? clazz.hashCode() : 0;
            result = 31 * result + (annotation != null ? annotation.hashCode() : 0);
            return result;
        }
    }


    // cache follows..

    private final Object classAndAnnotationsLock = new Object();
    private final Map> classAndAnnotation2Methods = new HashMap>();
    private final Map, List> class2AllMethodsWithAnnotations = new HashMap, List>();

    /**
     * Find all annotated method from a class
     * @param clazz The class
     * @param annotationType The annotation class
     * @return A list of method object
     */
    public List findAllAnnotatedMethods(Class clazz, Class annotationType) {

        if( clazz == null ) {
            return new ArrayList(0);
        }

        synchronized( classAndAnnotationsLock ) {

            // first look in cache

            ClassAndAnnotation key = new ClassAndAnnotation(clazz, annotationType);

            List methods = classAndAnnotation2Methods.get( key );
            if( methods != null ) {
                // cache hit
                return methods;
            }
            // have to resolve it.
            methods = new ArrayList();
            // get list of all annotated methods on this class..
            for( Method method : findAllAnnotatedMethods( clazz)) {
                if (method.isAnnotationPresent(annotationType)) {
                    methods.add(method);
                }
            }

            // store it in cache
            classAndAnnotation2Methods.put( key, methods);

            return methods;
        }
    }

    /**
     * Find all annotated method from a class
     * @param clazz The class
     * @return A list of method object
     */
    public List findAllAnnotatedMethods(Class clazz) {
        synchronized( classAndAnnotationsLock ) {
            // first check the cache..
            List methods = class2AllMethodsWithAnnotations.get(clazz);
            if( methods != null ) {
                // cache hit
                return methods;
            }
            //have to resolve it..
            methods = new ArrayList();
            // Clazz can be null if we are looking at an interface / annotation
            while (clazz != null && !clazz.equals(Object.class)) {
                for (Method method : clazz.getDeclaredMethods()) {
                    if (method.getAnnotations().length > 0) {
                        methods.add(method);
                    }
                }
                if (clazz.isAnnotationPresent(With.class)) {
                    for (Class withClass : clazz.getAnnotation(With.class).value()) {
                        methods.addAll(findAllAnnotatedMethods(withClass ));
                    }
                }
                clazz = clazz.getSuperclass();
            }

            //store it in the cache.
            class2AllMethodsWithAnnotations.put(clazz, methods);
            return methods;
        }
    }


}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy