|
| 1 | +package com.example.utils |
| 2 | + |
| 3 | +import org.objectweb.asm.* |
| 4 | +import java.lang.reflect.Field |
| 5 | +import java.lang.reflect.Method |
| 6 | +import java.lang.reflect.Modifier |
| 7 | +import java.util.* |
| 8 | + |
| 9 | +@Retention(AnnotationRetention.RUNTIME) |
| 10 | +@Target(AnnotationTarget.CLASS) |
| 11 | +annotation class LoadKt |
| 12 | + |
| 13 | +@Retention(AnnotationRetention.RUNTIME) |
| 14 | +@Target(AnnotationTarget.CLASS) |
| 15 | +annotation class LoadJ |
| 16 | + |
| 17 | +interface LoadService { |
| 18 | + fun loadAll(packageName: String) |
| 19 | +} |
| 20 | + |
| 21 | +object LoadUtil { |
| 22 | + fun loadAll(packageName: String) { |
| 23 | + val loader: ServiceLoader<LoadService> = ServiceLoader.load(LoadService::class.java) |
| 24 | + for (service in loader) { |
| 25 | + service.loadAll(packageName) |
| 26 | + } |
| 27 | + } |
| 28 | + |
| 29 | + private val loadedClasses = mutableSetOf<String>() |
| 30 | + fun processClass(className: String, classLoader: ClassLoader = Thread.currentThread().contextClassLoader) { |
| 31 | + if (loadedClasses.contains(className)) { |
| 32 | + return // Class already processed, this is probably a companion object |
| 33 | + } |
| 34 | + loadedClasses.add(className) |
| 35 | + if (hasAnnotation(className, "Lcom/example/utils/LoadKt;")) { |
| 36 | + val clazz = classLoader.loadClass(className) |
| 37 | + if (clazz.isAnnotationPresent(LoadJ::class.java)) { |
| 38 | + throw Exception("Class $clazz cannot be annotated with both LoadKt and LoadJ") |
| 39 | + } |
| 40 | + |
| 41 | + if (clazz.kotlin.isCompanion) { |
| 42 | + throw Exception( |
| 43 | + "Class $clazz cannot be a companion object. " + |
| 44 | + "Please keep the companion object and move the annotation to the class." |
| 45 | + ) |
| 46 | + } |
| 47 | + |
| 48 | + val instance: Field? = try { |
| 49 | + clazz.getDeclaredField("INSTANCE") |
| 50 | + } catch (_: NoSuchFieldException) { |
| 51 | + try { |
| 52 | + clazz.getDeclaredField("Companion") |
| 53 | + } catch (_: NoSuchFieldException) { |
| 54 | + null |
| 55 | + } |
| 56 | + } |
| 57 | + |
| 58 | + if (instance == null) { |
| 59 | + throw Exception("Class $clazz annotated with LoadKt must have a field named INSTANCE or Companion") |
| 60 | + } |
| 61 | + |
| 62 | + if (!Modifier.isStatic(instance.modifiers)) { |
| 63 | + throw Exception("Field INSTANCE or Companion in class $clazz annotated with LoadKt must be static") |
| 64 | + } |
| 65 | + |
| 66 | + val loadMethod: Method? = try { |
| 67 | + instance.type.getDeclaredMethod("load") |
| 68 | + } catch (_: NoSuchMethodException) { |
| 69 | + null |
| 70 | + } |
| 71 | + |
| 72 | + checkMethod(loadMethod) |
| 73 | + |
| 74 | + loadMethod!!.invoke(instance.get(null)) |
| 75 | + } else if (hasAnnotation(className, "Lcom/example/utils/LoadJ;")) { |
| 76 | + val clazz = classLoader.loadClass(className) |
| 77 | + val loadMethod: Method = try { |
| 78 | + clazz.getDeclaredMethod("load") |
| 79 | + } catch (_: NoSuchMethodException) { |
| 80 | + throw Exception("Class $clazz annotated with LoadJ must have a method named load") |
| 81 | + } |
| 82 | + |
| 83 | + checkMethod(loadMethod) |
| 84 | + |
| 85 | + if (!Modifier.isStatic(loadMethod.modifiers)) { |
| 86 | + throw Exception("Method ${loadMethod.name} in class ${loadMethod.declaringClass} must be static") |
| 87 | + } |
| 88 | + |
| 89 | + loadMethod.invoke(null) |
| 90 | + } |
| 91 | + } |
| 92 | + |
| 93 | + private fun checkMethod(method: Method?) { |
| 94 | + if (method == null) { |
| 95 | + throw Exception("Method must not be null") |
| 96 | + } |
| 97 | + |
| 98 | + if (method.parameterCount != 0) { |
| 99 | + throw Exception("Method ${method.name} in class ${method.declaringClass} must have no parameters") |
| 100 | + } |
| 101 | + |
| 102 | + if (method.returnType != Void.TYPE) { |
| 103 | + throw Exception("Method ${method.name} in class ${method.declaringClass} must return Void") |
| 104 | + } |
| 105 | + } |
| 106 | + |
| 107 | + @Throws(java.lang.Exception::class) |
| 108 | + fun hasAnnotation(className: String, annotationDescriptor: String): Boolean { |
| 109 | + val found = booleanArrayOf(false) |
| 110 | + val stream = this::class.java.classLoader.getResourceAsStream(className.replace('.', '/') + ".class") |
| 111 | + val reader = try { ClassReader(stream) } catch (_: Exception) { |
| 112 | + throw Exception("Class $className not found or cannot be read") |
| 113 | + } |
| 114 | + reader.accept(object : ClassVisitor(Opcodes.ASM9) { |
| 115 | + override fun visitAnnotation(desc: String, visible: Boolean): AnnotationVisitor? { |
| 116 | + if (desc == annotationDescriptor) { |
| 117 | + found[0] = true |
| 118 | + } |
| 119 | + return super.visitAnnotation(desc, visible) |
| 120 | + } |
| 121 | + }, 0) |
| 122 | + return found[0] |
| 123 | + } |
| 124 | +} |
| 125 | + |
0 commit comments