package com.patchworkmc.event; import org.objectweb.asm.AnnotationVisitor; import org.objectweb.asm.ClassVisitor; import org.objectweb.asm.MethodVisitor; import org.objectweb.asm.Opcodes; /** * Processes Cancelable and HasResult annotations, blocks overriding getListenerList and getParentListenerList. */ public class EventSubclassTransformer extends ClassVisitor { private static final String CANCELABLE_ANNOTATION = "Lnet/minecraftforge/eventbus/api/Cancelable;"; private static final String HAS_RESULT_ANNOTATION = "Lnet/minecraftforge/eventbus/api/Event$HasResult;"; private static final String IS_CANCELABLE = "isCancelable"; private static final String BOOLEAN_DESCRIPTOR = "()Z"; private static final String HAS_RESULT = "hasResult"; private static final String GET_LISTENER_LIST = "getListenerList"; private static final String GET_PARENT_LISTENER_LIST = "getParentListenerList"; private static final String GET_LISTENER_LIST_DESCRIPTOR = "()Lnet/minecraftforge/eventbus/ListenerList;"; private boolean cancelable; private boolean hasCancelable; private boolean hasResult; private boolean hasHasResult; private String className; public EventSubclassTransformer(ClassVisitor parent) { super(Opcodes.ASM7, parent); } @Override public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) { super.visit(version, access, name, signature, superName, interfaces); this.className = name; } @Override public AnnotationVisitor visitAnnotation(String descriptor, boolean visible) { if (descriptor.equals(CANCELABLE_ANNOTATION)) { cancelable = true; return null; } else if (descriptor.equals(HAS_RESULT_ANNOTATION)) { hasResult = true; return null; } return super.visitAnnotation(descriptor, visible); } @Override public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) { // Strip getListenerList and getParentListenerList to significantly simplify the logic in Patchwork EventBus. if (name.equals(GET_LISTENER_LIST) && descriptor.equals(GET_LISTENER_LIST_DESCRIPTOR)) { String error = String.format("Patchwork does not support overriding %s in %s (an assumed Event class)", GET_LISTENER_LIST, className); throw new UnsupportedOperationException(error); } if (name.equals(GET_PARENT_LISTENER_LIST) && descriptor.equals(GET_LISTENER_LIST_DESCRIPTOR)) { String error = String.format("Patchwork does not support overriding %s in %s (an assumed Event class)", GET_PARENT_LISTENER_LIST, className); throw new UnsupportedOperationException(error); } // Keep track of if (name.equals(IS_CANCELABLE) && descriptor.equals(BOOLEAN_DESCRIPTOR)) { hasCancelable = true; } else if (name.equals(HAS_RESULT) && descriptor.equals(BOOLEAN_DESCRIPTOR)) { hasHasResult = true; } return super.visitMethod(access, name, descriptor, signature, exceptions); } @Override public void visitEnd() { if (cancelable && !hasCancelable) { visitMarkerMethod(IS_CANCELABLE); } if (hasResult && !hasHasResult) { visitMarkerMethod(HAS_RESULT); } super.visitEnd(); } /** * Adds a public marker method that returns true. * * <p>It appears like so: <pre> * public boolean name() { * return true; * } * </pre></p> * * @param name The name of the generated method */ private void visitMarkerMethod(String name) { MethodVisitor isCancelable = super.visitMethod(Opcodes.ACC_PUBLIC, name, BOOLEAN_DESCRIPTOR, null, null); if (isCancelable != null) { AnnotationVisitor override = isCancelable.visitAnnotation("Ljava/lang/Override;", true); if (override != null) { override.visitEnd(); } isCancelable.visitInsn(Opcodes.ICONST_1); isCancelable.visitInsn(Opcodes.IRETURN); isCancelable.visitMaxs(2, 1); isCancelable.visitEnd(); } } }