Commit 58a0b66325b564f439c5c896c38343cdb5723939
1 parent
3b64e194
support for boolean returns in HandlerList using logical operations between handlers
Showing
3 changed files
with
211 additions
and
18 deletions
java/common/com/mumfrey/liteloader/core/event/HandlerList.java
@@ -20,6 +20,7 @@ import org.objectweb.asm.Type; | @@ -20,6 +20,7 @@ import org.objectweb.asm.Type; | ||
20 | import org.objectweb.asm.tree.*; | 20 | import org.objectweb.asm.tree.*; |
21 | 21 | ||
22 | import com.mumfrey.liteloader.core.runtime.Obf; | 22 | import com.mumfrey.liteloader.core.runtime.Obf; |
23 | +import com.mumfrey.liteloader.interfaces.FastIterableDeque; | ||
23 | import com.mumfrey.liteloader.util.log.LiteLoaderLogger; | 24 | import com.mumfrey.liteloader.util.log.LiteLoaderLogger; |
24 | 25 | ||
25 | /** | 26 | /** |
@@ -30,18 +31,59 @@ import com.mumfrey.liteloader.util.log.LiteLoaderLogger; | @@ -30,18 +31,59 @@ import com.mumfrey.liteloader.util.log.LiteLoaderLogger; | ||
30 | * | 31 | * |
31 | * @param <T> | 32 | * @param <T> |
32 | */ | 33 | */ |
33 | -public class HandlerList<T> extends LinkedList<T> | 34 | +public class HandlerList<T> extends LinkedList<T> implements FastIterableDeque<T> |
34 | { | 35 | { |
35 | private static final long serialVersionUID = 1L; | 36 | private static final long serialVersionUID = 1L; |
36 | 37 | ||
37 | private static final int MAX_UNCOLLECTED_CLASSES = 5000; | 38 | private static final int MAX_UNCOLLECTED_CLASSES = 5000; |
38 | 39 | ||
39 | private static int uncollectedHandlerLists = 0; | 40 | private static int uncollectedHandlerLists = 0; |
41 | + | ||
42 | + /** | ||
43 | + * Enum for logic operations supported between handlers which return bool | ||
44 | + */ | ||
45 | + public enum ReturnLogicOp | ||
46 | + { | ||
47 | + /** | ||
48 | + * Logical OR applied between handlers, return FALSE unless one or more handlers returns TRUE | ||
49 | + */ | ||
50 | + OR, | ||
51 | + | ||
52 | + /** | ||
53 | + * Logical OR, but with the difference than an EMPTY handler list will return TRUE | ||
54 | + */ | ||
55 | + OR_ASSUME_TRUE, | ||
56 | + | ||
57 | + /** | ||
58 | + * Logical AND, returns TRUE if the list is empty or if all handlers return TRUE | ||
59 | + */ | ||
60 | + AND, | ||
61 | + | ||
62 | + /** | ||
63 | + * Logical AND, returns FALSE at the first handler to return FALSE and doesn't process any further handlers | ||
64 | + */ | ||
65 | + AND_BREAK_ON_FALSE; | ||
66 | + | ||
67 | + boolean isOr() | ||
68 | + { | ||
69 | + return this == OR || this == OR_ASSUME_TRUE; | ||
70 | + } | ||
71 | + | ||
72 | + boolean assumeTrue() | ||
73 | + { | ||
74 | + return this == OR_ASSUME_TRUE; | ||
75 | + } | ||
76 | + } | ||
40 | 77 | ||
41 | /** | 78 | /** |
42 | * Type of the interface for objects in this handler list | 79 | * Type of the interface for objects in this handler list |
43 | */ | 80 | */ |
44 | - private Class<T> type; | 81 | + private final Class<T> type; |
82 | + | ||
83 | + /** | ||
84 | + * | ||
85 | + */ | ||
86 | + private final ReturnLogicOp logicOp; | ||
45 | 87 | ||
46 | /** | 88 | /** |
47 | * Current baked handler list, we cook them at gas mark 5 for 30 minutes in a disposable classloader whic | 89 | * Current baked handler list, we cook them at gas mark 5 for 30 minutes in a disposable classloader whic |
@@ -54,12 +96,22 @@ public class HandlerList<T> extends LinkedList<T> | @@ -54,12 +96,22 @@ public class HandlerList<T> extends LinkedList<T> | ||
54 | */ | 96 | */ |
55 | public HandlerList(Class<T> type) | 97 | public HandlerList(Class<T> type) |
56 | { | 98 | { |
99 | + this(type, ReturnLogicOp.AND_BREAK_ON_FALSE); | ||
100 | + } | ||
101 | + | ||
102 | + /** | ||
103 | + * @param type | ||
104 | + * @param logicOp Logical operation to apply to interface methods which return boolean | ||
105 | + */ | ||
106 | + public HandlerList(Class<T> type, ReturnLogicOp logicOp) | ||
107 | + { | ||
57 | if (!type.isInterface()) | 108 | if (!type.isInterface()) |
58 | { | 109 | { |
59 | throw new IllegalArgumentException("HandlerList type argument must be an interface"); | 110 | throw new IllegalArgumentException("HandlerList type argument must be an interface"); |
60 | } | 111 | } |
61 | 112 | ||
62 | this.type = type; | 113 | this.type = type; |
114 | + this.logicOp = logicOp; | ||
63 | } | 115 | } |
64 | 116 | ||
65 | /** | 117 | /** |
@@ -67,6 +119,7 @@ public class HandlerList<T> extends LinkedList<T> | @@ -67,6 +119,7 @@ public class HandlerList<T> extends LinkedList<T> | ||
67 | * | 119 | * |
68 | * @return | 120 | * @return |
69 | */ | 121 | */ |
122 | + @Override | ||
70 | public T all() | 123 | public T all() |
71 | { | 124 | { |
72 | if (this.bakedHandler == null) | 125 | if (this.bakedHandler == null) |
@@ -82,13 +135,14 @@ public class HandlerList<T> extends LinkedList<T> | @@ -82,13 +135,14 @@ public class HandlerList<T> extends LinkedList<T> | ||
82 | */ | 135 | */ |
83 | protected void bake() | 136 | protected void bake() |
84 | { | 137 | { |
85 | - HandlerListClassLoader<T> classLoader = new HandlerListClassLoader<T>(this.type); | 138 | + HandlerListClassLoader<T> classLoader = new HandlerListClassLoader<T>(this.type, this.logicOp); |
86 | this.bakedHandler = classLoader.newHandler(this); | 139 | this.bakedHandler = classLoader.newHandler(this); |
87 | } | 140 | } |
88 | 141 | ||
89 | /** | 142 | /** |
90 | * Invalidate current baked list | 143 | * Invalidate current baked list |
91 | */ | 144 | */ |
145 | + @Override | ||
92 | public void invalidate() | 146 | public void invalidate() |
93 | { | 147 | { |
94 | if (this.bakedHandler == null) | 148 | if (this.bakedHandler == null) |
@@ -194,7 +248,16 @@ public class HandlerList<T> extends LinkedList<T> | @@ -194,7 +248,16 @@ public class HandlerList<T> extends LinkedList<T> | ||
194 | @Override | 248 | @Override |
195 | public boolean addAll(Collection<? extends T> listeners) | 249 | public boolean addAll(Collection<? extends T> listeners) |
196 | { | 250 | { |
197 | - throw new UnsupportedOperationException("'addAll' is not supported for HandlerList"); | 251 | + for (T listener : listeners) |
252 | + { | ||
253 | + if (!this.contains(listener)) | ||
254 | + { | ||
255 | + super.add(listener); | ||
256 | + } | ||
257 | + } | ||
258 | + | ||
259 | + this.invalidate(); | ||
260 | + return true; | ||
198 | } | 261 | } |
199 | 262 | ||
200 | /* (non-Javadoc) | 263 | /* (non-Javadoc) |
@@ -413,6 +476,11 @@ public class HandlerList<T> extends LinkedList<T> | @@ -413,6 +476,11 @@ public class HandlerList<T> extends LinkedList<T> | ||
413 | private final String typeRef; | 476 | private final String typeRef; |
414 | 477 | ||
415 | /** | 478 | /** |
479 | + * Logic operation to apply when running a callback with a boolean | ||
480 | + */ | ||
481 | + private final ReturnLogicOp logicOp; | ||
482 | + | ||
483 | + /** | ||
416 | * Size of the handler list | 484 | * Size of the handler list |
417 | */ | 485 | */ |
418 | private int size; | 486 | private int size; |
@@ -421,11 +489,12 @@ public class HandlerList<T> extends LinkedList<T> | @@ -421,11 +489,12 @@ public class HandlerList<T> extends LinkedList<T> | ||
421 | * @param type | 489 | * @param type |
422 | * @param size | 490 | * @param size |
423 | */ | 491 | */ |
424 | - HandlerListClassLoader(Class<T> type) | 492 | + HandlerListClassLoader(Class<T> type, ReturnLogicOp logicOp) |
425 | { | 493 | { |
426 | super(new URL[0], Launch.classLoader); | 494 | super(new URL[0], Launch.classLoader); |
427 | this.type = type; | 495 | this.type = type; |
428 | this.typeRef = type.getName().replace('.', '/'); | 496 | this.typeRef = type.getName().replace('.', '/'); |
497 | + this.logicOp = logicOp; | ||
429 | } | 498 | } |
430 | 499 | ||
431 | /** | 500 | /** |
@@ -506,6 +575,8 @@ public class HandlerList<T> extends LinkedList<T> | @@ -506,6 +575,8 @@ public class HandlerList<T> extends LinkedList<T> | ||
506 | classNode.accept(classWriter); | 575 | classNode.accept(classWriter); |
507 | bytes = classWriter.toByteArray(); | 576 | bytes = classWriter.toByteArray(); |
508 | 577 | ||
578 | +// classNode.accept(new org.objectweb.asm.util.CheckClassAdapter(new ClassWriter(ClassWriter.COMPUTE_MAXS | ClassWriter.COMPUTE_FRAMES))); | ||
579 | + | ||
509 | // Delegate to ClassLoader's usual behaviour to load the class we just generated | 580 | // Delegate to ClassLoader's usual behaviour to load the class we just generated |
510 | return this.defineClass(name, bytes, 0, bytes.length); | 581 | return this.defineClass(name, bytes, 0, bytes.length); |
511 | } | 582 | } |
@@ -614,6 +685,9 @@ public class HandlerList<T> extends LinkedList<T> | @@ -614,6 +685,9 @@ public class HandlerList<T> extends LinkedList<T> | ||
614 | 685 | ||
615 | method.instructions.add(new VarInsnNode(Opcodes.ALOAD, 0)); | 686 | method.instructions.add(new VarInsnNode(Opcodes.ALOAD, 0)); |
616 | method.instructions.add(new InsnNode(Opcodes.ARETURN)); | 687 | method.instructions.add(new InsnNode(Opcodes.ARETURN)); |
688 | + | ||
689 | + method.maxStack = 1; | ||
690 | + method.maxLocals = 1; | ||
617 | } | 691 | } |
618 | 692 | ||
619 | /** | 693 | /** |
@@ -639,6 +713,9 @@ public class HandlerList<T> extends LinkedList<T> | @@ -639,6 +713,9 @@ public class HandlerList<T> extends LinkedList<T> | ||
639 | 713 | ||
640 | method.instructions.add(new VarInsnNode(Opcodes.ALOAD, 0)); | 714 | method.instructions.add(new VarInsnNode(Opcodes.ALOAD, 0)); |
641 | method.instructions.add(new InsnNode(Opcodes.ARETURN)); | 715 | method.instructions.add(new InsnNode(Opcodes.ARETURN)); |
716 | + | ||
717 | + method.maxStack = 3; | ||
718 | + method.maxLocals = 2; | ||
642 | } | 719 | } |
643 | 720 | ||
644 | /** | 721 | /** |
@@ -675,24 +752,88 @@ public class HandlerList<T> extends LinkedList<T> | @@ -675,24 +752,88 @@ public class HandlerList<T> extends LinkedList<T> | ||
675 | private void populateInterfaceMethod(ClassNode classNode, MethodNode method) | 752 | private void populateInterfaceMethod(ClassNode classNode, MethodNode method) |
676 | { | 753 | { |
677 | Type returnType = Type.getReturnType(method.desc); | 754 | Type returnType = Type.getReturnType(method.desc); |
755 | + Type[] args = Type.getArgumentTypes(method.desc); | ||
678 | 756 | ||
679 | if (returnType.equals(Type.VOID_TYPE)) | 757 | if (returnType.equals(Type.VOID_TYPE)) |
680 | { | 758 | { |
681 | - Type[] args = Type.getArgumentTypes(method.desc); | ||
682 | method.access = Opcodes.ACC_PUBLIC; | 759 | method.access = Opcodes.ACC_PUBLIC; |
760 | + this.populateVoidInvokationChain(classNode, method, args); | ||
761 | + } | ||
762 | + else if (returnType.equals(Type.BOOLEAN_TYPE)) | ||
763 | + { | ||
764 | + method.access = Opcodes.ACC_PUBLIC; | ||
765 | + this.populateBooleanInvokationChain(classNode, method, args); | ||
766 | + } | ||
767 | + } | ||
768 | + | ||
769 | + /** | ||
770 | + * @param classNode | ||
771 | + * @param method | ||
772 | + * @param args | ||
773 | + */ | ||
774 | + private void populateVoidInvokationChain(ClassNode classNode, MethodNode method, Type[] args) | ||
775 | + { | ||
776 | + for (int handlerIndex = 0; handlerIndex < this.size; handlerIndex++) | ||
777 | + { | ||
778 | + this.invokeHandler(handlerIndex, classNode, method, args); | ||
779 | + } | ||
780 | + | ||
781 | + method.instructions.add(new InsnNode(Opcodes.RETURN)); | ||
782 | + | ||
783 | + method.maxLocals = args.length + 1; | ||
784 | + method.maxStack = args.length + 1; | ||
785 | + } | ||
786 | + | ||
787 | + /** | ||
788 | + * @param classNode | ||
789 | + * @param method | ||
790 | + * @param args | ||
791 | + */ | ||
792 | + private void populateBooleanInvokationChain(ClassNode classNode, MethodNode method, Type[] args) | ||
793 | + { | ||
794 | + boolean isOrOperation = this.logicOp.isOr(); | ||
795 | + boolean breakOnFalse = this.logicOp == ReturnLogicOp.AND_BREAK_ON_FALSE; | ||
796 | + int initialValue = isOrOperation && (!this.logicOp.assumeTrue() || this.size > 0) ? Opcodes.ICONST_0 : Opcodes.ICONST_1; | ||
797 | + int localIndex = this.getFirstLocalIndex(args); | ||
798 | + | ||
799 | + method.instructions.add(new InsnNode(initialValue)); | ||
800 | + method.instructions.add(new VarInsnNode(Opcodes.ISTORE, localIndex)); | ||
801 | + | ||
802 | + for (int handlerIndex = 0; handlerIndex < this.size; handlerIndex++) | ||
803 | + { | ||
804 | + this.invokeHandler(handlerIndex, classNode, method, args); // invoke the method, this will leave the return value on the stack | ||
683 | 805 | ||
684 | - for (int handlerIndex = 0; handlerIndex < this.size; handlerIndex++) | ||
685 | - { | ||
686 | - LabelNode lineNumberLabel = new LabelNode(new Label()); | ||
687 | - method.instructions.add(lineNumberLabel); | ||
688 | - method.instructions.add(new LineNumberNode(100 + handlerIndex, lineNumberLabel)); | ||
689 | - method.instructions.add(new VarInsnNode(Opcodes.ALOAD, 0)); | ||
690 | - method.instructions.add(new FieldInsnNode(Opcodes.GETFIELD, classNode.name, HandlerListClassLoader.HANDLER_VAR_PREFIX + handlerIndex, "L" + this.typeRef + ";")); | ||
691 | - this.invokeInterfaceMethod(method, args); | ||
692 | - } | 806 | + int jumpCondition = isOrOperation ? Opcodes.IFEQ : Opcodes.IFNE; // jump if zero for OR, jump if one for AND |
807 | + int semaphore = isOrOperation ? Opcodes.ICONST_1 : Opcodes.ICONST_0; // will push TRUE for OR, will push FALSE for AND | ||
693 | 808 | ||
694 | - method.instructions.add(new InsnNode(Opcodes.RETURN)); | ||
695 | - } | 809 | + LabelNode lbl = new LabelNode(); |
810 | + method.instructions.add(new JumpInsnNode(jumpCondition, lbl)); // jump over the set/return based on the condition | ||
811 | + method.instructions.add(new InsnNode(semaphore)); // push TRUE or FALSE onto the stack | ||
812 | + method.instructions.add(breakOnFalse ? new InsnNode(Opcodes.IRETURN) : new VarInsnNode(Opcodes.ISTORE, localIndex)); // set local or return | ||
813 | + method.instructions.add(lbl); // jump here | ||
814 | + } | ||
815 | + | ||
816 | + method.instructions.add(new VarInsnNode(Opcodes.ILOAD, localIndex)); | ||
817 | + method.instructions.add(new InsnNode(Opcodes.IRETURN)); | ||
818 | + | ||
819 | + method.maxLocals = args.length + 2; | ||
820 | + method.maxStack = args.length + 1; | ||
821 | + } | ||
822 | + | ||
823 | + /** | ||
824 | + * @param handlerIndex | ||
825 | + * @param classNode | ||
826 | + * @param method | ||
827 | + * @param args | ||
828 | + */ | ||
829 | + private int invokeHandler(int handlerIndex, ClassNode classNode, MethodNode method, Type[] args) | ||
830 | + { | ||
831 | + LabelNode lineNumberLabel = new LabelNode(new Label()); | ||
832 | + method.instructions.add(lineNumberLabel); | ||
833 | + method.instructions.add(new LineNumberNode(100 + handlerIndex, lineNumberLabel)); | ||
834 | + method.instructions.add(new VarInsnNode(Opcodes.ALOAD, 0)); | ||
835 | + method.instructions.add(new FieldInsnNode(Opcodes.GETFIELD, classNode.name, HandlerListClassLoader.HANDLER_VAR_PREFIX + handlerIndex, "L" + this.typeRef + ";")); | ||
836 | + return this.invokeInterfaceMethod(method, args); | ||
696 | } | 837 | } |
697 | 838 | ||
698 | /** | 839 | /** |
@@ -701,7 +842,7 @@ public class HandlerList<T> extends LinkedList<T> | @@ -701,7 +842,7 @@ public class HandlerList<T> extends LinkedList<T> | ||
701 | * @param method | 842 | * @param method |
702 | * @param args | 843 | * @param args |
703 | */ | 844 | */ |
704 | - private void invokeInterfaceMethod(MethodNode method, Type[] args) | 845 | + private int invokeInterfaceMethod(MethodNode method, Type[] args) |
705 | { | 846 | { |
706 | int argNumber = 1; | 847 | int argNumber = 1; |
707 | for (Type type : args) | 848 | for (Type type : args) |
@@ -711,6 +852,14 @@ public class HandlerList<T> extends LinkedList<T> | @@ -711,6 +852,14 @@ public class HandlerList<T> extends LinkedList<T> | ||
711 | } | 852 | } |
712 | 853 | ||
713 | method.instructions.add(new MethodInsnNode(Opcodes.INVOKEINTERFACE, this.typeRef, method.name, method.desc, true)); | 854 | method.instructions.add(new MethodInsnNode(Opcodes.INVOKEINTERFACE, this.typeRef, method.name, method.desc, true)); |
855 | + return argNumber; | ||
856 | + } | ||
857 | + | ||
858 | + private int getFirstLocalIndex(Type[] args) | ||
859 | + { | ||
860 | + int argNumber = 1; | ||
861 | + for (Type type : args) argNumber += type.getSize(); | ||
862 | + return argNumber; | ||
714 | } | 863 | } |
715 | 864 | ||
716 | /** | 865 | /** |
java/common/com/mumfrey/liteloader/interfaces/FastIterable.java
0 → 100644
1 | +package com.mumfrey.liteloader.interfaces; | ||
2 | + | ||
3 | +/** | ||
4 | + * Interface for objects which can return a baked list view of their list contents | ||
5 | + * | ||
6 | + * @author Adam Mummery-Smith | ||
7 | + * | ||
8 | + * @param <T> | ||
9 | + */ | ||
10 | +public interface FastIterable<T> | ||
11 | +{ | ||
12 | + /** | ||
13 | + * Add an entry to the iterable | ||
14 | + * | ||
15 | + * @param entry | ||
16 | + */ | ||
17 | + public boolean add(T entry); | ||
18 | + | ||
19 | + /** | ||
20 | + * Return the baked view of all entries | ||
21 | + * | ||
22 | + * @return | ||
23 | + */ | ||
24 | + public T all(); | ||
25 | + | ||
26 | + /** | ||
27 | + * Invalidate (force rebake of) the baked entry list | ||
28 | + */ | ||
29 | + public void invalidate(); | ||
30 | +} |
java/common/com/mumfrey/liteloader/interfaces/FastIterableDeque.java
0 → 100644