Commit 58a0b66325b564f439c5c896c38343cdb5723939

Authored by Mumfrey
1 parent 3b64e194

support for boolean returns in HandlerList using logical operations between handlers

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&lt;T&gt; extends LinkedList&lt;T&gt; @@ -54,12 +96,22 @@ public class HandlerList&lt;T&gt; extends LinkedList&lt;T&gt;
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&lt;T&gt; extends LinkedList&lt;T&gt; @@ -67,6 +119,7 @@ public class HandlerList&lt;T&gt; extends LinkedList&lt;T&gt;
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&lt;T&gt; extends LinkedList&lt;T&gt; @@ -82,13 +135,14 @@ public class HandlerList&lt;T&gt; extends LinkedList&lt;T&gt;
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&lt;T&gt; extends LinkedList&lt;T&gt; @@ -194,7 +248,16 @@ public class HandlerList&lt;T&gt; extends LinkedList&lt;T&gt;
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&lt;T&gt; extends LinkedList&lt;T&gt; @@ -413,6 +476,11 @@ public class HandlerList&lt;T&gt; extends LinkedList&lt;T&gt;
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&lt;T&gt; extends LinkedList&lt;T&gt; @@ -421,11 +489,12 @@ public class HandlerList&lt;T&gt; extends LinkedList&lt;T&gt;
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&lt;T&gt; extends LinkedList&lt;T&gt; @@ -506,6 +575,8 @@ public class HandlerList&lt;T&gt; extends LinkedList&lt;T&gt;
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&lt;T&gt; extends LinkedList&lt;T&gt; @@ -614,6 +685,9 @@ public class HandlerList&lt;T&gt; extends LinkedList&lt;T&gt;
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&lt;T&gt; extends LinkedList&lt;T&gt; @@ -639,6 +713,9 @@ public class HandlerList&lt;T&gt; extends LinkedList&lt;T&gt;
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&lt;T&gt; extends LinkedList&lt;T&gt; @@ -675,24 +752,88 @@ public class HandlerList&lt;T&gt; extends LinkedList&lt;T&gt;
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&lt;T&gt; extends LinkedList&lt;T&gt; @@ -701,7 +842,7 @@ public class HandlerList&lt;T&gt; extends LinkedList&lt;T&gt;
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&lt;T&gt; extends LinkedList&lt;T&gt; @@ -711,6 +852,14 @@ public class HandlerList&lt;T&gt; extends LinkedList&lt;T&gt;
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
  1 +package com.mumfrey.liteloader.interfaces;
  2 +
  3 +import java.util.Deque;
  4 +
  5 +/**
  6 + * Deque interface which is FastIterable
  7 + *
  8 + * @author Adam Mummery-Smith
  9 + *
  10 + * @param <T>
  11 + */
  12 +public interface FastIterableDeque<T> extends FastIterable<T>, Deque<T>
  13 +{
  14 +}