org.apache.s4.core.gen.OverloadDispatcherGenerator.java Source code

Java tutorial

Introduction

Here is the source code for org.apache.s4.core.gen.OverloadDispatcherGenerator.java

Source

/**
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you 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
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * 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.
 */

package org.apache.s4.core.gen;

import static org.objectweb.asm.Opcodes.ACC_PUBLIC;
import static org.objectweb.asm.Opcodes.ACC_SUPER;
import static org.objectweb.asm.Opcodes.ALOAD;
import static org.objectweb.asm.Opcodes.ASTORE;
import static org.objectweb.asm.Opcodes.CHECKCAST;
import static org.objectweb.asm.Opcodes.DUP;
import static org.objectweb.asm.Opcodes.F_APPEND;
import static org.objectweb.asm.Opcodes.IFEQ;
import static org.objectweb.asm.Opcodes.INSTANCEOF;
import static org.objectweb.asm.Opcodes.INVOKEINTERFACE;
import static org.objectweb.asm.Opcodes.INVOKESPECIAL;
import static org.objectweb.asm.Opcodes.INVOKESTATIC;
import static org.objectweb.asm.Opcodes.INVOKEVIRTUAL;
import static org.objectweb.asm.Opcodes.NEW;
import static org.objectweb.asm.Opcodes.RETURN;
import static org.objectweb.asm.Opcodes.V1_6;

import java.io.File;
import java.io.IOException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Random;

import org.apache.s4.base.Event;
import org.apache.s4.base.util.S4RLoader;
import org.apache.s4.core.ProcessingElement;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.Label;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;
import org.slf4j.LoggerFactory;

import com.google.common.io.Files;

/**
 * This class generates a proxy to enable dispatching of events to methods of processing elements based on the runtime
 * type of the event.
 * 
 * <p>
 * When an event is transferred to a processing element, the generated proxy finds the corresponding
 * <code>onEvent</code> method with the event type argument matching the current parameter and calls this method.
 * </p>
 * <p>
 * If there is no exact match, the closest type in the hierarchy of events is used.
 * </p>
 * <p>
 * If there is still no match, an error statement is logged and the event is ignored (not processed).
 * </p>
 * 
 */
public class OverloadDispatcherGenerator {
    private final List<Hierarchy> inputEventHierarchies = new ArrayList<Hierarchy>();
    private final List<Hierarchy> outputEventHierarchies = new ArrayList<Hierarchy>();
    private Class<?> targetClass;
    private static final boolean DUMP = false;

    public OverloadDispatcherGenerator() {
    }

    public OverloadDispatcherGenerator(Class<?> targetClass) {
        this.targetClass = targetClass;

        for (Method method : targetClass.getMethods()) {
            if (method.getName().equals("onEvent") && method.getReturnType().equals(Void.TYPE)) {
                inputEventHierarchies.add(new Hierarchy(method.getParameterTypes()[0]));
            } else if (method.getName().equals("onTrigger") && method.getReturnType().equals(Void.TYPE)) {
                outputEventHierarchies.add(new Hierarchy(method.getParameterTypes()[0]));
            }
        }
        // order by most specialized types
        Collections.sort(inputEventHierarchies);
        Collections.sort(outputEventHierarchies);
    }

    public Class<?> generate() {
        Random rand = new Random(System.currentTimeMillis());
        String dispatcherClassName = "OverloadDispatcher" + (Math.abs(rand.nextInt() % 3256));

        // class headers
        ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES);
        // CheckClassAdapter cw = new CheckClassAdapter(cw1);
        cw.visit(V1_6, ACC_PUBLIC + ACC_SUPER, dispatcherClassName, null, Type.getInternalName(Object.class),
                new String[] { Type.getInternalName(OverloadDispatcher.class) });

        // constructor
        MethodVisitor mv1 = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
        mv1.visitCode();
        Label l0 = new Label();
        mv1.visitLabel(l0);
        mv1.visitVarInsn(ALOAD, 0);
        mv1.visitMethodInsn(Opcodes.INVOKESPECIAL, "java/lang/Object", "<init>", "()V");
        Label l1 = new Label();
        mv1.visitLabel(l1);
        mv1.visitInsn(RETURN);
        Label l2 = new Label();
        mv1.visitLabel(l2);
        mv1.visitLocalVariable("this", "Lio/s4/core/" + dispatcherClassName + ";", null, l0, l2, 0);
        mv1.visitMaxs(1, 1);

        mv1.visitEnd();

        // dispatch input events method
        generateEventDispatchMethod(cw, "dispatchEvent", inputEventHierarchies, "onEvent");
        // dispatch output events method
        generateEventDispatchMethod(cw, "dispatchTrigger", outputEventHierarchies, "onTrigger");

        cw.visitEnd();

        if (DUMP) {
            try {
                LoggerFactory.getLogger(getClass())
                        .debug("Dumping generated overload dispatcher class for PE of class [" + targetClass + "]");
                Files.write(cw.toByteArray(),
                        new File(System.getProperty("java.io.tmpdir") + "/" + dispatcherClassName + ".class"));
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return new OverloadDispatcherClassLoader(targetClass.getClassLoader())
                .loadClassFromBytes(dispatcherClassName, cw.toByteArray());

    }

    private void generateEventDispatchMethod(ClassWriter cw, String dispatchMethodName,
            List<Hierarchy> eventHierarchies, String processEventMethodName) {
        MethodVisitor mv2 = cw.visitMethod(ACC_PUBLIC, dispatchMethodName,
                "(" + Type.getType(ProcessingElement.class).getDescriptor()
                        + Type.getType(Event.class).getDescriptor() + ")V",
                null, null);
        mv2.visitCode();
        Label l3 = new Label();
        mv2.visitLabel(l3);
        mv2.visitVarInsn(ALOAD, 1);
        mv2.visitTypeInsn(CHECKCAST, Type.getInternalName(targetClass));
        mv2.visitVarInsn(ASTORE, 3);
        boolean first = true;
        Label aroundLabel = new Label();
        for (Hierarchy hierarchy : eventHierarchies) {
            if (first) {
                Label l4 = new Label();
                mv2.visitLabel(l4);
            }
            mv2.visitVarInsn(ALOAD, 2);
            mv2.visitTypeInsn(INSTANCEOF, Type.getInternalName(hierarchy.getTop()));

            Label l5 = new Label();
            mv2.visitJumpInsn(IFEQ, l5);

            Label l6 = new Label();
            mv2.visitLabel(l6);
            mv2.visitVarInsn(ALOAD, 3);
            mv2.visitVarInsn(ALOAD, 2);
            mv2.visitTypeInsn(CHECKCAST, Type.getInternalName(hierarchy.getTop()));
            mv2.visitMethodInsn(INVOKEVIRTUAL, Type.getInternalName(targetClass), processEventMethodName,
                    "(" + Type.getDescriptor(hierarchy.getTop()) + ")V");
            mv2.visitJumpInsn(Opcodes.GOTO, aroundLabel);
            mv2.visitLabel(l5);

            if (first) {
                mv2.visitFrame(F_APPEND, 1, new Object[] { Type.getInternalName(targetClass) }, 0, null);
                first = false;
            } else {
                mv2.visitFrame(Opcodes.F_SAME, 0, null, 0, null);
            }
        }
        addErrorLogStatement(mv2);
        if (eventHierarchies.size() > 0) {
            mv2.visitLabel(aroundLabel);
            mv2.visitFrame(Opcodes.F_SAME, 0, null, 0, null);
        }
        mv2.visitInsn(RETURN);
        Label l8 = new Label();
        mv2.visitLabel(l8);
        mv2.visitLocalVariable("pe", Type.getDescriptor(ProcessingElement.class), null, l3, l8, 1);
        mv2.visitLocalVariable("event", Type.getDescriptor(Event.class), null, l3, l8, 2);
        mv2.visitLocalVariable("typedPE", Type.getDescriptor(targetClass), null, l3, l8, 3);
        mv2.visitMaxs(4, 4);
        mv2.visitEnd();
    }

    private void addErrorLogStatement(MethodVisitor mv2) {
        mv2.visitVarInsn(ALOAD, 0);
        mv2.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Object", "getClass", "()Ljava/lang/Class;");
        mv2.visitMethodInsn(INVOKESTATIC, "org/slf4j/LoggerFactory", "getLogger",
                "(Ljava/lang/Class;)Lorg/slf4j/Logger;");
        mv2.visitTypeInsn(NEW, "java/lang/StringBuilder");
        mv2.visitInsn(DUP);
        mv2.visitLdcInsn("Cannot dispatch event of type [");
        mv2.visitMethodInsn(INVOKESPECIAL, "java/lang/StringBuilder", "<init>", "(Ljava/lang/String;)V");
        mv2.visitVarInsn(ALOAD, 2);
        mv2.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Object", "getClass", "()Ljava/lang/Class;");
        mv2.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Class", "getName", "()Ljava/lang/String;");
        mv2.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "append",
                "(Ljava/lang/String;)Ljava/lang/StringBuilder;");
        mv2.visitLdcInsn("] to PE of type [");
        mv2.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "append",
                "(Ljava/lang/String;)Ljava/lang/StringBuilder;");
        mv2.visitVarInsn(ALOAD, 1);
        mv2.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Object", "getClass", "()Ljava/lang/Class;");
        mv2.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Class", "getName", "()Ljava/lang/String;");
        mv2.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "append",
                "(Ljava/lang/String;)Ljava/lang/StringBuilder;");
        mv2.visitLdcInsn("] : no matching onEvent method found");
        mv2.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "append",
                "(Ljava/lang/String;)Ljava/lang/StringBuilder;");
        mv2.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "toString", "()Ljava/lang/String;");
        mv2.visitMethodInsn(INVOKEINTERFACE, "org/slf4j/Logger", "error", "(Ljava/lang/String;)V");
    }

    // useful for classifying event classes by most specialized
    static class Hierarchy implements Comparable<Hierarchy> {
        private final List<Class<?>> classes = new ArrayList<Class<?>>();

        public Hierarchy(Class<?> clazz) {
            for (Class<?> currentClass = clazz; currentClass != null; currentClass = currentClass.getSuperclass()) {
                classes.add(currentClass);
            }
        }

        public Class<?> getTop() {
            if (classes.size() < 1) {
                return null;
            }
            return classes.get(0);
        }

        public boolean equals(Hierarchy other) {
            if (classes.size() != other.classes.size()) {
                return false;
            }

            for (int i = 0; i < classes.size(); i++) {
                if (!classes.get(i).equals(other.classes.get(i))) {
                    return false;
                }
            }

            return true;
        }

        @Override
        public int compareTo(Hierarchy other) {
            if (this.equals(other)) {
                return 0;
            } else if (this.containsClass(other.getTop())) {
                return -1;
            }

            return 1;
        }

        private boolean containsClass(Class<?> other) {
            for (Class<?> clazz : classes) {
                if (clazz.equals(other)) {
                    return true;
                }
            }
            return false;
        }
    }

    /**
     * 
     * Delegates to S4Classloader if it was used to load the PE prototype class, by passing to S4Classloader the
     * generated bytecode.
     * 
     * Falls back to parent classloader otherwise.
     * 
     */
    private static class OverloadDispatcherClassLoader extends ClassLoader {

        ClassLoader s4AppLoader;

        public OverloadDispatcherClassLoader(ClassLoader s4AppLoader) {
            this.s4AppLoader = s4AppLoader;
        }

        public Class<?> loadClassFromBytes(String name, byte[] bytes) {

            if (s4AppLoader instanceof S4RLoader) {
                return ((S4RLoader) s4AppLoader).loadGeneratedClass(name, bytes);
            } else {
                try {
                    return this.loadClass(name);
                } catch (ClassNotFoundException cnfe) {
                    // expected
                }
                return this.defineClass(name, bytes, 0, bytes.length);
            }
        }

    }
}