| /** |
| * 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 = true; |
| |
| 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); |
| } |
| } |
| |
| } |
| } |