/* * This file is part of LanternServer, licensed under the MIT License (MIT). * * Copyright (c) LanternPowered <https://www.lanternpowered.org> * Copyright (c) SpongePowered <https://www.spongepowered.org> * Copyright (c) contributors * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the Software), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ package org.lanternpowered.server.event; import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkNotNull; import static org.objectweb.asm.Opcodes.AALOAD; import static org.objectweb.asm.Opcodes.ACC_FINAL; import static org.objectweb.asm.Opcodes.ACC_PRIVATE; import static org.objectweb.asm.Opcodes.ACC_PUBLIC; import static org.objectweb.asm.Opcodes.ACC_STATIC; 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.BIPUSH; import static org.objectweb.asm.Opcodes.CHECKCAST; import static org.objectweb.asm.Opcodes.DUP; import static org.objectweb.asm.Opcodes.GETFIELD; import static org.objectweb.asm.Opcodes.GETSTATIC; import static org.objectweb.asm.Opcodes.IFNULL; import static org.objectweb.asm.Opcodes.INVOKEINTERFACE; import static org.objectweb.asm.Opcodes.INVOKESPECIAL; import static org.objectweb.asm.Opcodes.INVOKEVIRTUAL; import static org.objectweb.asm.Opcodes.NEW; import static org.objectweb.asm.Opcodes.PUTSTATIC; import static org.objectweb.asm.Opcodes.RETURN; import static org.objectweb.asm.Opcodes.V1_6; import com.github.benmanes.caffeine.cache.CacheLoader; import com.github.benmanes.caffeine.cache.Caffeine; import com.github.benmanes.caffeine.cache.LoadingCache; import org.lanternpowered.server.event.filter.EventFilter; import org.lanternpowered.server.event.filter.FilterFactory; import org.lanternpowered.server.event.gen.DefineableClassLoader; import org.objectweb.asm.ClassWriter; import org.objectweb.asm.FieldVisitor; import org.objectweb.asm.Label; import org.objectweb.asm.MethodVisitor; import org.objectweb.asm.Type; import org.spongepowered.api.event.Event; import org.spongepowered.api.util.generator.event.factory.ClassGenerator; import java.lang.reflect.Method; import java.util.concurrent.atomic.AtomicInteger; final class ClassEventListenerFactory implements AnnotatedEventListener.Factory { private final AtomicInteger id = new AtomicInteger(); private final DefineableClassLoader classLoader; private final LoadingCache<Method, Class<? extends AnnotatedEventListener>> cache = Caffeine.newBuilder().weakValues().build((CacheLoader<Method, Class<? extends AnnotatedEventListener>>) this::createClass); private FilterFactory filterFactory; private final String targetPackage; ClassEventListenerFactory(String targetPackage, FilterFactory factory, DefineableClassLoader classLoader) { checkNotNull(targetPackage, "targetPackage"); checkArgument(!targetPackage.isEmpty(), "targetPackage cannot be empty"); this.targetPackage = targetPackage + '.'; this.filterFactory = checkNotNull(factory, "filterFactory"); this.classLoader = checkNotNull(classLoader, "classLoader"); } @Override public AnnotatedEventListener create(Object handle, Method method) throws Exception { return this.cache.get(method) .getConstructor(method.getDeclaringClass()) .newInstance(handle); } private Class<? extends AnnotatedEventListener> createClass(Method method) throws Exception { Class<?> handle = method.getDeclaringClass(); Class<?> eventClass = method.getParameterTypes()[0]; String name = this.targetPackage + eventClass.getSimpleName() + "Listener_" + handle.getSimpleName() + '_' + method.getName() + this.id.incrementAndGet(); Class<? extends EventFilter> filter = this.filterFactory.createFilter(method); if (filter == null && method.getParameterCount() != 1) { // basic sanity check throw new IllegalStateException("Failed to generate EventFilter for non trivial filtering operation."); } if (filter != null) { filter.newInstance(); return this.classLoader.defineClass(name, generateClass(name, handle, method, eventClass, filter)); } return this.classLoader.defineClass(name, generateClass(name, handle, method, eventClass)); } private static final String BASE_HANDLER = Type.getInternalName(AnnotatedEventListener.class); private static final String HANDLE_METHOD_DESCRIPTOR = '(' + Type.getDescriptor(Event.class) + ")V"; private static final String FILTER_DESCRIPTOR = "(" + Type.getDescriptor(Event.class) + ")[Ljava/lang/Object;"; private static byte[] generateClass(String name, Class<?> handle, Method method, Class<?> eventClass, Class<? extends EventFilter> filter) { name = name.replace('.', '/'); final String handleName = Type.getInternalName(handle); final String handleDescriptor = Type.getDescriptor(handle); final String filterName = Type.getInternalName(filter); String eventDescriptor = "("; for (int i = 0; i < method.getParameterCount(); i++) { eventDescriptor += Type.getDescriptor(method.getParameterTypes()[i]); } eventDescriptor += ")V"; ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES | ClassWriter.COMPUTE_MAXS); MethodVisitor mv; FieldVisitor fv; cw.visit(V1_6, ACC_PUBLIC + ACC_FINAL + ACC_SUPER, name, null, BASE_HANDLER, null); { fv = cw.visitField(ACC_PRIVATE + ACC_STATIC, "FILTER", "L" + filterName + ";", null, null); fv.visitEnd(); } { mv = cw.visitMethod(ACC_STATIC, "<clinit>", "()V", null, null); mv.visitCode(); mv.visitTypeInsn(NEW, filterName); mv.visitInsn(DUP); mv.visitMethodInsn(INVOKESPECIAL, filterName, "<init>", "()V", false); mv.visitFieldInsn(PUTSTATIC, name, "FILTER", "L" + filterName + ";"); mv.visitInsn(RETURN); mv.visitMaxs(0, 0); mv.visitEnd(); } { mv = cw.visitMethod(ACC_PUBLIC, "<init>", '(' + handleDescriptor + ")V", null, null); mv.visitCode(); mv.visitVarInsn(ALOAD, 0); mv.visitVarInsn(ALOAD, 1); mv.visitMethodInsn(INVOKESPECIAL, BASE_HANDLER, "<init>", "(Ljava/lang/Object;)V", false); mv.visitInsn(RETURN); mv.visitMaxs(0, 0); mv.visitEnd(); } { mv = cw.visitMethod(ACC_PUBLIC, "handle", HANDLE_METHOD_DESCRIPTOR, null, new String[] { "java/lang/Exception" }); mv.visitCode(); mv.visitFieldInsn(GETSTATIC, name, "FILTER", "L" + filterName + ";"); mv.visitVarInsn(ALOAD, 1); mv.visitMethodInsn(INVOKEINTERFACE, Type.getInternalName(EventFilter.class), "filter", FILTER_DESCRIPTOR, true); mv.visitVarInsn(ASTORE, 2); mv.visitVarInsn(ALOAD, 2); Label l2 = new Label(); mv.visitJumpInsn(IFNULL, l2); mv.visitVarInsn(ALOAD, 0); mv.visitFieldInsn(GETFIELD, name, "handle", "Ljava/lang/Object;"); mv.visitTypeInsn(CHECKCAST, handleName); for (int i = 0; i < method.getParameterCount(); i++) { mv.visitVarInsn(ALOAD, 2); mv.visitIntInsn(BIPUSH, i); mv.visitInsn(AALOAD); Type paramType = Type.getType(method.getParameterTypes()[i]); ClassGenerator.visitUnboxingMethod(mv, paramType); } mv.visitMethodInsn(INVOKEVIRTUAL, handleName, method.getName(), eventDescriptor, false); mv.visitLabel(l2); mv.visitInsn(RETURN); mv.visitMaxs(0, 0); mv.visitEnd(); } cw.visitEnd(); return cw.toByteArray(); } private static byte[] generateClass(String name, Class<?> handle, Method method, Class<?> eventClass) { name = name.replace('.', '/'); final String handleName = Type.getInternalName(handle); final String handleDescriptor = Type.getDescriptor(handle); final String eventName = Type.getInternalName(eventClass); ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES | ClassWriter.COMPUTE_MAXS); MethodVisitor mv; cw.visit(V1_6, ACC_PUBLIC + ACC_FINAL + ACC_SUPER, name, null, BASE_HANDLER, null); { mv = cw.visitMethod(ACC_PUBLIC, "<init>", '(' + handleDescriptor + ")V", null, null); mv.visitCode(); mv.visitVarInsn(ALOAD, 0); mv.visitVarInsn(ALOAD, 1); mv.visitMethodInsn(INVOKESPECIAL, BASE_HANDLER, "<init>", "(Ljava/lang/Object;)V", false); mv.visitInsn(RETURN); mv.visitMaxs(0, 0); mv.visitEnd(); } { mv = cw.visitMethod(ACC_PUBLIC, "handle", HANDLE_METHOD_DESCRIPTOR, null, null); mv.visitCode(); mv.visitVarInsn(ALOAD, 0); mv.visitFieldInsn(GETFIELD, name, "handle", "Ljava/lang/Object;"); mv.visitTypeInsn(CHECKCAST, handleName); mv.visitVarInsn(ALOAD, 1); mv.visitTypeInsn(CHECKCAST, eventName); mv.visitMethodInsn(INVOKEVIRTUAL, handleName, method.getName(), "(L" + eventName + ";)V", false); mv.visitInsn(RETURN); mv.visitMaxs(0, 0); mv.visitEnd(); } cw.visitEnd(); return cw.toByteArray(); } }