Package co.paralleluniverse.pulsar

Source Code of co.paralleluniverse.pulsar.ClojureHelper$DumpClassListener

/*
* Pulsar: lightweight threads and Erlang-like actors for Clojure.
* Copyright (C) 2013-2014, Parallel Universe Software Co. All rights reserved.
*
* This program and the accompanying materials are dual-licensed under
* either the terms of the Eclipse Public License v1.0 as published by
* the Eclipse Foundation
*
*   or (per the licensee's choosing)
*
* under the terms of the GNU Lesser General Public License version 3.0
* as published by the Free Software Foundation.
*/
package co.paralleluniverse.pulsar;

import clojure.lang.IFn;
import clojure.lang.Var;
import co.paralleluniverse.actors.ActorRegistry;
import co.paralleluniverse.fibers.Instrumented;
import co.paralleluniverse.fibers.SuspendExecution;
import co.paralleluniverse.fibers.instrument.JavaAgent;
import co.paralleluniverse.fibers.instrument.MethodDatabase;
import co.paralleluniverse.fibers.instrument.MethodDatabase.ClassEntry;
import co.paralleluniverse.fibers.instrument.Retransform;
import co.paralleluniverse.strands.SuspendableCallable;
import java.lang.instrument.UnmodifiableClassException;
import java.lang.reflect.Method;
import java.util.Collection;
import java.util.HashSet;
import java.util.Map;
import java.util.Objects;
import java.util.Set;

import com.google.common.base.Function;
import org.objectweb.asm.Type;

/**
*
* @author pron
*/
public class ClojureHelper {
    // This whole mess with lastInstrumented is a heuristic to save us from calling clazz.isAnnotationPresent(Instrumented.class)),
    // which turns out to be *slow*.
    private static final int NUM_LAST_INSTRUMENTED = 3;
    private static final ThreadLocal<Class[]> lastInstrumented = new ThreadLocal<Class[]>() {
        @Override
        protected Class[] initialValue() {
            return new Class[NUM_LAST_INSTRUMENTED];
        }
    };
    private static final ThreadLocal<Int> lastInstrumentedIndex = new ThreadLocal<Int>() {
        @Override
        protected Int initialValue() {
            return new Int();
        }
    };

    static {
        if(!JavaAgent.isActive())
            throw new RuntimeException("Java agent not running");

        // These methods need not be instrumented. we mark them so that verifyInstrumentation doesn't fail when they're on the call-stack
        Retransform.addWaiver("clojure.lang.AFn", "applyToHelper");
        Retransform.addWaiver("clojure.lang.AFn", "applyTo");
        Retransform.addWaiver("clojure.lang.RestFn", "invoke");
        Retransform.addWaiver("clojure.lang.RestFn", "doInvoke");
        Retransform.addWaiver("clojure.core$apply", "invoke");
        Retransform.addWaiver("co.paralleluniverse.pulsar.InstrumentedIFn", "invoke");

        Retransform.addWaiver("co.paralleluniverse.actors.behaviors.EventHandler", "handleEvent");

        // mark all IFn methods as suspendable
        Retransform.getMethodDB().getClassEntry(Type.getInternalName(IFn.class)).setAll(MethodDatabase.SuspendableType.SUSPENDABLE_SUPER);

        // register kryo serializers for clojure types
        if (ActorRegistry.hasGlobalRegistry()) {
            try {
                Class.forName("co.paralleluniverse.pulsar.galaxy.ClojureKryoSerializers");
            } catch (ClassNotFoundException e) {
                throw new AssertionError(e);
            }
        }
    }

    public static Object retransform(Object thing, Collection<Class> protocols) throws UnmodifiableClassException {
        if (thing instanceof IInstrumented)
            return thing;

        if (isInLastInstrumented(thing.getClass()))
            return new InstrumentedIFn((IFn) thing);
        return retransform1(thing, protocols);
    }

    private static Object retransform1(Object thing, Collection<Class> protocols) throws UnmodifiableClassException {
        final boolean isClass = thing instanceof Class;
        final Class clazz = isClass ? (Class) thing : thing.getClass();

        final boolean isIFn = IFn.class.isAssignableFrom(clazz);

        if (IInstrumented.class.isAssignableFrom(clazz) || clazz.isAnnotationPresent(Instrumented.class)) {
            if (isIFn) {
                addToLastInstrumented(clazz);
                return !isClass ? new InstrumentedIFn((IFn) thing) : thing;
            } else
                return thing;
        }

        if (!isIFn && clazz.isInterface()) {
            Retransform.getMethodDB().getClassEntry(Type.getInternalName(clazz)).setAll(MethodDatabase.SuspendableType.SUSPENDABLE_SUPER);
            return thing;
        }

        if (!isIFn && protocols == null)
            throw new IllegalArgumentException("Cannot retransform " + thing + ". Not an IFn and a protocol not given");

        final Set<String> protocolMethods;
        if (!isIFn) {
            protocolMethods = new HashSet<String>();
            for (Class protocol : protocols)
                for (Method m : protocol.getMethods())
                    protocolMethods.add(m.getName());
        } else
            protocolMethods = null;

//        if (!isIFn) {
//            Retransform.addClassLoadListener(new DumpClassListener(clazz));
//        }

        try {
            // Clojure might break up a single function into several classes. We must instrument them all.
            for (Map.Entry<String, ClassEntry> entry : Retransform.getMethodDB().getInnerClassesEntries(Type.getInternalName(clazz)).entrySet()) {
                final String className = entry.getKey();
                final ClassEntry ce = entry.getValue();
                final Class cls = Class.forName(className.replaceAll("/", "."), false, clazz.getClassLoader());
                //System.out.println("---- " + cls + " " + IFn.class.isAssignableFrom(cls));
                ce.setRequiresInstrumentation(true);
                Method[] methods = cls.getMethods();

                for (Method method : methods) {
                    if ((IFn.class.isAssignableFrom(cls) && (method.getName().equals("invoke") || method.getName().equals("doInvoke")))
                            || (cls == clazz && !isIFn && protocolMethods.contains(method.getName()))) { // method.getDeclaringClass().equals(clazz))) {
                        ce.set(method.getName(), Type.getMethodDescriptor(method), MethodDatabase.SuspendableType.SUSPENDABLE);
                    }
                }
                Retransform.retransform(cls);
            }
        } catch (ClassNotFoundException e) {
            throw new RuntimeException(e);
        }

        if (isIFn) {
            addToLastInstrumented(clazz);
            return !isClass ? new InstrumentedIFn((IFn) thing) : thing;
        } else
            return thing;
    }

    ////////
    public static SuspendableCallable<Object> asSuspendableCallable(final IFn fn) {
        if (fn == null)
            return null;
        if (!(fn instanceof InstrumentedIFn))
            throw new IllegalArgumentException("Function " + fn + " has not been instrumented");

        final Object binding = Var.cloneThreadBindingFrame(); // Clojure treats bindings as an InheritableThreadLocal, yet sets them in a ThreadLocal...
        return new SuspendableCallable<Object>() {
            @Override
            public Object run() throws SuspendExecution, InterruptedException {
                Var.resetThreadBindingFrame(binding);
                return suspendableInvoke(fn);
//                final Object origBinding = Var.getThreadBindingFrame();
//                try {
//                    Var.resetThreadBindingFrame(binding);
//                    return suspendableInvoke(fn);
//                } finally {
//                    Var.resetThreadBindingFrame(origBinding);
//                }
            }
        };
    }

    static Object suspendableInvoke(IFn fn) throws SuspendExecution {
        return fn.invoke();
    }

    public static boolean isInstrumented(Class clazz) {
        return clazz.isAnnotationPresent(Instrumented.class);
    }

    private static boolean isInLastInstrumented(Class cls) {
        Class[] cs = lastInstrumented.get();
        for (Class c : cs) {
            if (c == cls)
                return true;
        }
        return false;
    }

    private static void addToLastInstrumented(Class cls) {
        Int ind = lastInstrumentedIndex.get();
        lastInstrumented.get()[ind.i] = cls;
        ind.i = (ind.i + 1) % NUM_LAST_INSTRUMENTED;
    }

    private static class Int {
        public int i;
    }

    private static Collection<Class<?>> supers(Class<?> c, Collection<Class<?>> s) {
        if (c == null)
            return s;

        s.add(c);
        for (Class iface : c.getInterfaces())
            supers(iface, s);
        supers(c.getSuperclass(), s);

        return s;
    }

    private static class DumpClassListener implements Retransform.ClassLoadListener {
        private final Class clazz;

        public DumpClassListener(Class clazz) {
            this.clazz = clazz;
        }

        @Override
        public void beforeTransform(String className, Class clazz, byte[] data) {
            if (clazz.equals(this.clazz)) {
                System.out.println("=== BEFORE ================================================");
                Retransform.dumpClass(className, data);
            }
        }

        @Override
        public void afterTransform(String className, Class clazz, byte[] data) {
            if (clazz.equals(this.clazz)) {
                System.out.println("=== AFTER ================================================");
                Retransform.dumpClass(className, data);
            }
        }

        @Override
        public int hashCode() {
            int hash = 3;
            hash = 31 * hash + Objects.hashCode(this.clazz);
            return hash;
        }

        @Override
        public boolean equals(Object obj) {
            if (obj == null)
                return false;
            if (getClass() != obj.getClass())
                return false;
            final DumpClassListener other = (DumpClassListener) obj;
            if (!Objects.equals(this.clazz, other.clazz))
                return false;
            return true;
        }
    }
}
TOP

Related Classes of co.paralleluniverse.pulsar.ClojureHelper$DumpClassListener

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.