migration
This commit is contained in:
20
annotation-processor/build.gradle
Normal file
20
annotation-processor/build.gradle
Normal file
@@ -0,0 +1,20 @@
|
||||
plugins {
|
||||
id 'java'
|
||||
}
|
||||
|
||||
group = 'com.github.hdvtdev'
|
||||
version = '1.0.0'
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation 'com.google.auto.service:auto-service:1.1.1'
|
||||
implementation project(':core')
|
||||
implementation 'org.ow2.asm:asm:9.9'
|
||||
annotationProcessor 'com.google.auto.service:auto-service:1.1.1'
|
||||
//implementation 'com.fasterxml.jackson.core:jackson-annotations:2.18.3'
|
||||
implementation project(":event-handlers-annotations")
|
||||
}
|
||||
|
||||
172
annotation-processor/src/main/java/EventHandlersProcessor.java
Normal file
172
annotation-processor/src/main/java/EventHandlersProcessor.java
Normal file
@@ -0,0 +1,172 @@
|
||||
import com.google.auto.service.AutoService;
|
||||
|
||||
import javax.annotation.processing.*;
|
||||
import javax.lang.model.SourceVersion;
|
||||
import javax.lang.model.element.*;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
|
||||
import hdvtdev.telegram.handler.annotations.*;
|
||||
|
||||
@AutoService(Processor.class)
|
||||
@SupportedAnnotationTypes({
|
||||
"hdvtdev.telegram.handler.annotations.OnChosenInlineResult",
|
||||
"hdvtdev.telegram.handler.annotations.OnChatMemberUpdated",
|
||||
"hdvtdev.telegram.handler.annotations.OnChatBoostRemoved",
|
||||
"hdvtdev.telegram.handler.annotations.OnPreCheckoutQuery",
|
||||
"hdvtdev.telegram.handler.annotations.OnBusinessConnection",
|
||||
"hdvtdev.telegram.handler.annotations.OnPaidMediaPurchased",
|
||||
"hdvtdev.telegram.handler.annotations.OnUpdate",
|
||||
"hdvtdev.telegram.handler.annotations.BotCommand",
|
||||
"hdvtdev.telegram.handler.annotations.OnPoll",
|
||||
"hdvtdev.telegram.handler.annotations.OnMessage",
|
||||
"hdvtdev.telegram.handler.annotations.OnChatJoinRequest",
|
||||
"hdvtdev.telegram.handler.annotations.OnChatBoostUpdated",
|
||||
"hdvtdev.telegram.handler.annotations.OnCallbackQuery",
|
||||
"hdvtdev.telegram.handler.annotations.OnShippingQuery",
|
||||
"hdvtdev.telegram.handler.annotations.OnInlineQuery",
|
||||
"hdvtdev.telegram.handler.annotations.OnPollAnswer"})
|
||||
@SupportedSourceVersion(SourceVersion.RELEASE_21)
|
||||
public class EventHandlersProcessor extends AbstractProcessor {
|
||||
|
||||
private final StringBuilder sb = new StringBuilder();
|
||||
|
||||
private static final String PACKAGE = "hdvtdev.telegram.handler.annotations.";
|
||||
private static final String HANDLER_PACKAGE = "hdvtdev.telegram.handler.";
|
||||
|
||||
private static final Set<String> SUPPORTED_ANNOTATIONS = Set.of(
|
||||
OnChosenInlineResult.class,
|
||||
OnChatMemberUpdated.class,
|
||||
OnChatBoostRemoved.class,
|
||||
OnPreCheckoutQuery.class,
|
||||
OnBusinessConnection.class,
|
||||
OnPaidMediaPurchased.class,
|
||||
OnUpdate.class,
|
||||
OnPoll.class,
|
||||
OnMessage.class,
|
||||
OnChatJoinRequest.class,
|
||||
OnChatBoostUpdated.class,
|
||||
OnCallbackQuery.class,
|
||||
OnShippingQuery.class,
|
||||
OnInlineQuery.class,
|
||||
OnPollAnswer.class
|
||||
).stream().map(Class::getName).collect(Collectors.toSet());
|
||||
|
||||
private static final Map<String, String> SUPPORTED_PARAMETERS = Map.ofEntries(
|
||||
Map.entry("Update", "hdvtdev.telegram.core.objects.Update"),
|
||||
Map.entry("InlineQuery", "hdvtdev.telegram.core.objects.InlineQuery"),
|
||||
Map.entry("ChatMemberUpdated", "hdvtdev.telegram.core.objects.chat.ChatMemberUpdated"),
|
||||
Map.entry("ChatBoostRemoved", "hdvtdev.telegram.core.objects.chatboost.ChatBoostRemoved"),
|
||||
Map.entry("PreCheckoutQuery", "hdvtdev.telegram.core.objects.payment.PreCheckoutQuery"),
|
||||
Map.entry("BusinessConnection", "hdvtdev.telegram.core.objects.business.BusinessConnection"),
|
||||
Map.entry("PaidMediaPurchased", "hdvtdev.telegram.core.objects.media.paidmedia.PaidMediaPurchased"),
|
||||
Map.entry("Poll", "hdvtdev.telegram.core.objects.poll.Poll"),
|
||||
Map.entry("Message", "hdvtdev.telegram.core.objects.Message"),
|
||||
Map.entry("ChatJoinRequest", "hdvtdev.telegram.core.objects.chat.ChatJoinRequest"),
|
||||
Map.entry("ChatBoostUpdated", "hdvtdev.telegram.core.objects.chatboost.ChatBoostUpdated"),
|
||||
Map.entry("CallbackQuery", "hdvtdev.telegram.core.objects.callback.CallbackQuery"),
|
||||
Map.entry("ShippingQuery", "hdvtdev.telegram.core.objects.payment.ShippingQuery"),
|
||||
Map.entry("PollAnswer", "hdvtdev.telegram.core.objects.poll.PollAnswer"),
|
||||
Map.entry("ChosenInlineResult", "hdvtdev.telegram.core.objects.ChosenInlineResult")
|
||||
);
|
||||
|
||||
public static String getFullParameterName(String simpleName) {
|
||||
return SUPPORTED_PARAMETERS.get(simpleName);
|
||||
}
|
||||
|
||||
private Messager messager;
|
||||
|
||||
@Override
|
||||
public synchronized void init(ProcessingEnvironment processingEnv) {
|
||||
super.init(processingEnv);
|
||||
messager = processingEnv.getMessager();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
|
||||
messager.printNote("Called");
|
||||
for (Element element : roundEnvironment.getRootElements()) {
|
||||
for (Element enclosedElement : element.getEnclosedElements()) {
|
||||
if (enclosedElement.getKind() == ElementKind.METHOD) {
|
||||
processMethod((ExecutableElement) enclosedElement);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (roundEnvironment.processingOver()) {
|
||||
write();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private void write() {
|
||||
|
||||
}
|
||||
|
||||
private void processMethod(ExecutableElement methodElement) {
|
||||
String methodName = methodElement.getSimpleName().toString();
|
||||
|
||||
List<AnnotationMirror> foundAnnotations = new ArrayList<>();
|
||||
for (AnnotationMirror mirror : methodElement.getAnnotationMirrors()) {
|
||||
String mirrorAnnotationName = Util.getAnnotationMirrorName(mirror);
|
||||
if (SUPPORTED_ANNOTATIONS.contains(PACKAGE + mirrorAnnotationName)) {
|
||||
foundAnnotations.add(mirror);
|
||||
}
|
||||
}
|
||||
|
||||
if (foundAnnotations.size() > 1) {
|
||||
List<String> annotationNames = new ArrayList<>();
|
||||
for (AnnotationMirror mirror : foundAnnotations) {
|
||||
annotationNames.add("@" + Util.getAnnotationMirrorName(mirror));
|
||||
}
|
||||
|
||||
messager.printError(String.format("Method %s cannot have multiple mutually exclusive annotations. Found: %s. Please leave only one.",
|
||||
methodName, String.join(", ", annotationNames)), methodElement);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (foundAnnotations.size() == 1) {
|
||||
AnnotationMirror annotation = foundAnnotations.getFirst();
|
||||
String annotationSimpleName = Util.getAnnotationMirrorName(annotation);
|
||||
|
||||
var params = methodElement.getParameters();
|
||||
if (params.isEmpty()) {
|
||||
messager.printError(String.format("Method %s, annotated with @%s, must have at least one parameter.",
|
||||
methodName, annotationSimpleName), methodElement);
|
||||
return;
|
||||
}
|
||||
|
||||
String firstParameterType = Util.getParameterClassName(params.getFirst().asType());
|
||||
String requiredParameter = Util.checkParameter(annotationSimpleName, firstParameterType);
|
||||
|
||||
if (!requiredParameter.isEmpty()) {
|
||||
messager.printError(String.format("Incorrect parameter type for method %s. Annotation @%s requires %s, but found %s.",
|
||||
methodName, annotationSimpleName, requiredParameter, firstParameterType), params.getFirst());
|
||||
return;
|
||||
}
|
||||
|
||||
if (!annotationSimpleName.equals("BotCommand")) {
|
||||
String paramName = params.getFirst().getSimpleName().toString();
|
||||
Set<String> filters = new HashSet<>();
|
||||
|
||||
AnnotationValue vv = Util.getAnnotationValue(annotation, "filters");
|
||||
@SuppressWarnings("unchecked")
|
||||
List<? extends AnnotationValue> enumValues = vv == null ? List.of() : (List<? extends AnnotationValue>) vv.getValue();
|
||||
for (AnnotationValue v : enumValues) {
|
||||
if (v.getValue() instanceof VariableElement enumConstant) {
|
||||
filters.add(enumConstant.getSimpleName().toString());
|
||||
}
|
||||
}
|
||||
sb.append(Generator.generateVoid(methodName, String.format("%s %s", firstParameterType, paramName),
|
||||
Generator.generateFilterBlock(filters, paramName)));
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
42
annotation-processor/src/main/java/Generator.java
Normal file
42
annotation-processor/src/main/java/Generator.java
Normal file
@@ -0,0 +1,42 @@
|
||||
import java.util.Set;
|
||||
|
||||
public final class Generator {
|
||||
|
||||
private Generator() {
|
||||
|
||||
}
|
||||
|
||||
public static String generateVoid(String name, String obj, String block) {
|
||||
return String.format("""
|
||||
public static void %s(%s) {
|
||||
%s
|
||||
}
|
||||
""", name, obj, block);
|
||||
}
|
||||
|
||||
public static String generateFilterBlock(Set<String> filters, String objName) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (String filter : filters) {
|
||||
boolean skip = true;
|
||||
sb.append("if (");
|
||||
sb.append(objName);
|
||||
sb.append(".");
|
||||
for (String part : filter.split("_")) {
|
||||
part = part.toLowerCase();
|
||||
if (skip) {
|
||||
sb.append(part);
|
||||
skip = false;
|
||||
} else {
|
||||
char[] chars = part.toCharArray();
|
||||
chars[0] = Character.toUpperCase(chars[0]);
|
||||
sb.append(chars);
|
||||
}
|
||||
}
|
||||
sb.append("()) {");
|
||||
sb.append("\n /* some code */ \n}\n");
|
||||
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
}
|
||||
108
annotation-processor/src/main/java/HandlerWriter.java
Normal file
108
annotation-processor/src/main/java/HandlerWriter.java
Normal file
@@ -0,0 +1,108 @@
|
||||
import org.objectweb.asm.*;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
|
||||
public class HandlerWriter {
|
||||
|
||||
public static void write(String body) throws IOException {
|
||||
|
||||
String classFilePath = "models/Handlers.class"; // Путь к вашему файлу
|
||||
Path path = Paths.get(classFilePath);
|
||||
|
||||
// 1. Читаем исходный класс в байты
|
||||
byte[] originalBytecode = Files.readAllBytes(path);
|
||||
|
||||
// 2. Инициализируем ASM
|
||||
ClassReader classReader = new ClassReader(originalBytecode);
|
||||
ClassWriter classWriter = new ClassWriter(ClassWriter.COMPUTE_FRAMES);
|
||||
|
||||
// 3. Создаем наш визитор, который найдет и заменит метод
|
||||
ClassVisitor classVisitor = new MethodReplacer(classWriter);
|
||||
|
||||
// 4. Запускаем процесс
|
||||
classReader.accept(classVisitor, ClassReader.EXPAND_FRAMES);
|
||||
|
||||
// 5. Получаем измененный байт-код
|
||||
byte[] modifiedBytecode = classWriter.toByteArray();
|
||||
|
||||
// 6. Перезаписываем исходный файл
|
||||
Files.write(path, modifiedBytecode);
|
||||
|
||||
System.out.println("Метод invoke в файле " + classFilePath + " был успешно заменен.");
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
private static class MethodReplacer extends ClassVisitor {
|
||||
|
||||
public MethodReplacer(ClassVisitor classVisitor) {
|
||||
super(Opcodes.ASM9, classVisitor);
|
||||
}
|
||||
|
||||
@Override
|
||||
public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) {
|
||||
String targetDescriptor = "(L" + EventHandlersProcessor.getFullParameterName("Update").replace('.', '/') +
|
||||
";)V";
|
||||
|
||||
if ("invoke".equals(name) && targetDescriptor.equals(descriptor)) {
|
||||
System.out.println("Найден метод 'invoke'. Заменяем его тело...");
|
||||
// Найден нужный метод. Не передаем его дальше (старое тело удаляется).
|
||||
// Вместо этого создаем новый MethodVisitor для генерации нового тела.
|
||||
MethodVisitor mv = cv.visitMethod(access, name, descriptor, signature, exceptions);
|
||||
generateNewBody(mv);
|
||||
// Возвращаем null, так как мы уже создали метод через cv.visitMethod
|
||||
return null;
|
||||
}
|
||||
|
||||
// Для всех остальных методов - просто передаем их дальше без изменений
|
||||
return super.visitMethod(access, name, descriptor, signature, exceptions);
|
||||
}
|
||||
|
||||
private void generateNewBody(MethodVisitor mv) {
|
||||
mv.visitCode();
|
||||
|
||||
mv.visitFieldInsn(Opcodes.GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
|
||||
mv.visitLdcInsn("Hello from the new invoke method!");
|
||||
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
|
||||
|
||||
// Код для: Integer updateId = update.getUpdateId();
|
||||
// `update` - это первый аргумент, поэтому он в локальной переменной 1 (0 это `this`)
|
||||
mv.visitVarInsn(Opcodes.ALOAD, 1);
|
||||
// Предполагаем, что getUpdateId() возвращает Integer. Если он возвращает int, нужно использовать valueOf.
|
||||
// Для примера, пусть он возвращает Integer.
|
||||
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, EventHandlersProcessor.getFullParameterName("Update"),
|
||||
"getUpdateId",
|
||||
"()Ljava/lang/Long;", false);
|
||||
// Сохраняем результат в локальную переменную 2
|
||||
mv.visitVarInsn(Opcodes.ASTORE, 2);
|
||||
|
||||
// Код для: System.out.println("Update ID: " + updateId);
|
||||
mv.visitFieldInsn(Opcodes.GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
|
||||
// Создаем новый StringBuilder для конкатенации строк
|
||||
mv.visitTypeInsn(Opcodes.NEW, "java/lang/StringBuilder");
|
||||
mv.visitInsn(Opcodes.DUP);
|
||||
mv.visitMethodInsn(Opcodes.INVOKESPECIAL, "java/lang/StringBuilder", "<init>", "()V", false);
|
||||
mv.visitLdcInsn("Update ID: ");
|
||||
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(Ljava/lang/String;)Ljava/lang/StringBuilder;", false);
|
||||
// Загружаем updateId из локальной переменной 2
|
||||
mv.visitVarInsn(Opcodes.ALOAD, 2);
|
||||
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(Ljava/lang/Object;)Ljava/lang/StringBuilder;", false);
|
||||
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/StringBuilder", "toString", "()Ljava/lang/String;", false);
|
||||
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
|
||||
|
||||
|
||||
// Завершаем метод (поскольку он void, используем RETURN)
|
||||
mv.visitInsn(Opcodes.RETURN);
|
||||
|
||||
// Указываем максимальный размер стека и количество локальных переменных.
|
||||
// ASM может вычислить это за нас, если использовать ClassWriter.COMPUTE_FRAMES
|
||||
mv.visitMaxs(0, 0); // Значения игнорируются при COMPUTE_FRAMES
|
||||
mv.visitEnd();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
63
annotation-processor/src/main/java/Util.java
Normal file
63
annotation-processor/src/main/java/Util.java
Normal file
@@ -0,0 +1,63 @@
|
||||
import javax.lang.model.element.AnnotationMirror;
|
||||
import javax.lang.model.element.AnnotationValue;
|
||||
import javax.lang.model.element.Element;
|
||||
import javax.lang.model.element.ExecutableElement;
|
||||
import javax.lang.model.type.DeclaredType;
|
||||
import javax.lang.model.type.TypeMirror;
|
||||
import java.util.Map;
|
||||
|
||||
public final class Util {
|
||||
|
||||
private Util() {
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
public static String checkParameter(String simpleAnnotationName, String simpleParameterName) {
|
||||
String reqParameter = switch (simpleAnnotationName) {
|
||||
case "OnMessage", "BotCommand" -> "Message";
|
||||
case "OnBusinessConnection" -> "BusinessConnection";
|
||||
case "OnCallbackQuery" -> "CallbackQuery";
|
||||
case "OnChatBoostRemoved" -> "ChatBoostRemoved";
|
||||
case "OnChatBoostUpdated" -> "ChatBoostUpdated";
|
||||
case "OnChatJoinRequest" -> "ChatJoinRequest";
|
||||
case "OnChatMemberUpdated" -> "ChatMemberUpdated";
|
||||
case "OnChosenInlineResult" -> "ChosenInlineResult";
|
||||
case "OnInlineQuery" -> "InlineQuery";
|
||||
case "OnPaidMediaPurchased" -> "PaidMediaPurchased";
|
||||
case "OnPoll" -> "Poll";
|
||||
case "OnPollAnswer" -> "PollAnswer";
|
||||
case "OnPreCheckoutQuery" -> "PreCheckoutQuery";
|
||||
case "OnShippingQuery" -> "ShippingQuery";
|
||||
case "OnUpdate" -> "Update";
|
||||
default -> throw new IllegalStateException("Unexpected value: " + simpleAnnotationName);
|
||||
};
|
||||
return reqParameter.equals(simpleParameterName) ? "" : reqParameter;
|
||||
}
|
||||
|
||||
public static String getParameterClassName(TypeMirror typeMirror) {
|
||||
String methodParameterName = "unknown (probably primitive type)";
|
||||
if (typeMirror instanceof DeclaredType declaredType) {
|
||||
Element typeElement = declaredType.asElement();
|
||||
methodParameterName = typeElement.getSimpleName().toString();
|
||||
}
|
||||
return methodParameterName;
|
||||
}
|
||||
|
||||
|
||||
public static String getAnnotationMirrorName(AnnotationMirror mirror) {
|
||||
return mirror.getAnnotationType().asElement().getSimpleName().toString();
|
||||
}
|
||||
|
||||
public static AnnotationValue getAnnotationValue(AnnotationMirror annotationMirror, String attributeName) {
|
||||
Map<? extends ExecutableElement, ? extends AnnotationValue> elementValues = annotationMirror.getElementValues();
|
||||
for (Map.Entry<? extends ExecutableElement, ? extends AnnotationValue> entry : elementValues.entrySet()) {
|
||||
if (entry.getKey().getSimpleName().toString().equals(attributeName)) {
|
||||
return entry.getValue();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user