Skip to content

Commit

Permalink
feat: dynamic create EventHandler using ow2 ASM
Browse files Browse the repository at this point in the history
  • Loading branch information
cnbrand committed Jun 21, 2024
1 parent 8038791 commit 682a2ce
Show file tree
Hide file tree
Showing 3 changed files with 124 additions and 1 deletion.
76 changes: 76 additions & 0 deletions shared/java/top/fpsmaster/event/ASMHandler.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
package top.fpsmaster.event

import org.objectweb.asm.ClassWriter
import org.objectweb.asm.MethodVisitor
import org.objectweb.asm.Opcodes.*
import org.objectweb.asm.Type
import top.fpsmaster.utils.java.EventClassLoader
import java.lang.reflect.Method

object ASMHandler {
private var index = 0
fun loadHandlerClass(listener: Any, method: Method): Handler {
val declaringClass = method.declaringClass
val className = "EventHandler$index"
val cv = ClassWriter(ClassWriter.COMPUTE_FRAMES or ClassWriter.COMPUTE_MAXS)

cv.visit(V1_8, ACC_PUBLIC or ACC_SUPER, className, null, Type.getInternalName(Handler::class.java), null)
cv.visitSource("$className.java", null)

// Original code: super(listener, method)
var mv: MethodVisitor = cv.visitMethod(ACC_PUBLIC, "<init>", "(Ljava/lang/Object;Ljava/lang/reflect/Method;)V", null, null)
mv.visitCode()
mv.visitVarInsn(ALOAD, 0)
mv.visitVarInsn(ALOAD, 1)
mv.visitVarInsn(ALOAD, 2)
mv.visitMethodInsn(INVOKESPECIAL, Type.getInternalName(Handler::class.java), "<init>", "(Ljava/lang/Object;Ljava/lang/reflect/Method;)V", false)
mv.visitInsn(RETURN)
mv.visitMaxs(3, 3)
mv.visitEnd()

// Original code: this.getMethod().invoke(this.getListener(), event)
mv = cv.visitMethod(ACC_PUBLIC, "invoke", "(L${Type.getInternalName(Event::class.java)};)V", null, null)
mv.visitCode()
mv.visitVarInsn(ALOAD, 0)
mv.visitMethodInsn(INVOKEVIRTUAL, className, "getMethod", "()Ljava/lang/reflect/Method;", false)
mv.visitVarInsn(ALOAD, 0)
mv.visitMethodInsn(INVOKEVIRTUAL, className, "getListener", "()Ljava/lang/Object;", false)
mv.visitInsn(ICONST_1)
mv.visitTypeInsn(ANEWARRAY, "java/lang/Object")
mv.visitInsn(DUP)
mv.visitInsn(ICONST_0)
mv.visitVarInsn(ALOAD, 1)
mv.visitInsn(AASTORE)
mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/reflect/Method", "invoke", "(Ljava/lang/Object;[Ljava/lang/Object;)Ljava/lang/Object;", false)
mv.visitInsn(POP)
mv.visitInsn(RETURN)
mv.visitMaxs(6, 3)
mv.visitEnd()

// Original code: this.getListener().getClass().getSimpleName() + " -> " + this.getMethod().getName()
mv = cv.visitMethod(ACC_PUBLIC, "getLog", "()Ljava/lang/String;", null, null)
mv.visitCode()
mv.visitTypeInsn(NEW, "java/lang/StringBuilder")
mv.visitInsn(DUP)
mv.visitMethodInsn(INVOKESPECIAL, "java/lang/StringBuilder", "<init>", "()V", false)
mv.visitVarInsn(ALOAD, 0)
mv.visitMethodInsn(INVOKEVIRTUAL, className, "getListener", "()Ljava/lang/Object;", false)
mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Object", "getClass", "()Ljava/lang/Class;", false)
mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Class", "getSimpleName", "()Ljava/lang/String;", false)
mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(Ljava/lang/String;)Ljava/lang/StringBuilder;", false)
mv.visitLdcInsn(" -> ")
mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(Ljava/lang/String;)Ljava/lang/StringBuilder;", false)
mv.visitVarInsn(ALOAD, 0)
mv.visitMethodInsn(INVOKEVIRTUAL, className, "getMethod", "()Ljava/lang/reflect/Method;", false)
mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/reflect/Method", "getName", "()Ljava/lang/String;", false)
mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(Ljava/lang/String;)Ljava/lang/StringBuilder;", false)
mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "toString", "()Ljava/lang/String;", false)
mv.visitInsn(ARETURN)
mv.visitMaxs(5, 1)
mv.visitEnd()

cv.visitEnd()

return EventClassLoader.defineClass(declaringClass.classLoader, className, cv.toByteArray()).getConstructor(Any::class.java, Method::class.java).newInstance(listener, method) as Handler
}
}
2 changes: 1 addition & 1 deletion shared/java/top/fpsmaster/event/EventDispatcher.kt
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ object EventDispatcher {
val eventType = parameterType as Class<out Event>
val listeners = eventListeners.computeIfAbsent(eventType) { _: Class<out Event>? -> CopyOnWriteArrayList() }
// listeners.add(ReflectHandler(listener, method))
listeners.add(LambdaHandler(listener, method))
listeners.add(ASMHandler.loadHandlerClass(listener, method))
}
}
}
Expand Down
47 changes: 47 additions & 0 deletions shared/java/top/fpsmaster/utils/java/EventClassLoader.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package top.fpsmaster.utils.java

import java.util.Collections
import java.util.WeakHashMap

object EventClassLoader {
private val loaders: MutableMap<ClassLoader, GeneratedClassLoader> = Collections.synchronizedMap(WeakHashMap())

fun defineClass(parentLoader: ClassLoader, name: String, data: ByteArray): Class<*> {
val loader = loaders.computeIfAbsent(parentLoader) { GeneratedClassLoader(it) }
synchronized(loader.getClassLoadingLock(name)) {
val clazz = loader.define(name, data)
assert(clazz.name == name)
return clazz
}
}

private class GeneratedClassLoader(parent: ClassLoader) : ClassLoader(parent) {
init {
ClassLoader.registerAsParallelCapable()
}

fun define(name: String, data: ByteArray): Class<*> {
synchronized(getClassLoadingLock(name)) {
assert(!hasClass(name))
val clazz = defineClass(name, data, 0, data.size)
resolveClass(clazz)
return clazz
}
}

public override fun getClassLoadingLock(name: String): Any {
return super.getClassLoadingLock(name)
}

fun hasClass(name: String): Boolean {
synchronized(getClassLoadingLock(name)) {
return try {
Class.forName(name)
true
} catch (e: ClassNotFoundException) {
false
}
}
}
}
}

0 comments on commit 682a2ce

Please sign in to comment.