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 20 import org.objectweb.asm.tree.*;
21 21  
22 22 import com.mumfrey.liteloader.core.runtime.Obf;
  23 +import com.mumfrey.liteloader.interfaces.FastIterableDeque;
23 24 import com.mumfrey.liteloader.util.log.LiteLoaderLogger;
24 25  
25 26 /**
... ... @@ -30,18 +31,59 @@ import com.mumfrey.liteloader.util.log.LiteLoaderLogger;
30 31 *
31 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 36 private static final long serialVersionUID = 1L;
36 37  
37 38 private static final int MAX_UNCOLLECTED_CLASSES = 5000;
38 39  
39 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 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 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 96 */
55 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 108 if (!type.isInterface())
58 109 {
59 110 throw new IllegalArgumentException("HandlerList type argument must be an interface");
60 111 }
61 112  
62 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 119 *
68 120 * @return
69 121 */
  122 + @Override
70 123 public T all()
71 124 {
72 125 if (this.bakedHandler == null)
... ... @@ -82,13 +135,14 @@ public class HandlerList&lt;T&gt; extends LinkedList&lt;T&gt;
82 135 */
83 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 139 this.bakedHandler = classLoader.newHandler(this);
87 140 }
88 141  
89 142 /**
90 143 * Invalidate current baked list
91 144 */
  145 + @Override
92 146 public void invalidate()
93 147 {
94 148 if (this.bakedHandler == null)
... ... @@ -194,7 +248,16 @@ public class HandlerList&lt;T&gt; extends LinkedList&lt;T&gt;
194 248 @Override
195 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 263 /* (non-Javadoc)
... ... @@ -413,6 +476,11 @@ public class HandlerList&lt;T&gt; extends LinkedList&lt;T&gt;
413 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 484 * Size of the handler list
417 485 */
418 486 private int size;
... ... @@ -421,11 +489,12 @@ public class HandlerList&lt;T&gt; extends LinkedList&lt;T&gt;
421 489 * @param type
422 490 * @param size
423 491 */
424   - HandlerListClassLoader(Class<T> type)
  492 + HandlerListClassLoader(Class<T> type, ReturnLogicOp logicOp)
425 493 {
426 494 super(new URL[0], Launch.classLoader);
427 495 this.type = type;
428 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 575 classNode.accept(classWriter);
507 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 580 // Delegate to ClassLoader's usual behaviour to load the class we just generated
510 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 685  
615 686 method.instructions.add(new VarInsnNode(Opcodes.ALOAD, 0));
616 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 713  
640 714 method.instructions.add(new VarInsnNode(Opcodes.ALOAD, 0));
641 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 752 private void populateInterfaceMethod(ClassNode classNode, MethodNode method)
676 753 {
677 754 Type returnType = Type.getReturnType(method.desc);
  755 + Type[] args = Type.getArgumentTypes(method.desc);
678 756  
679 757 if (returnType.equals(Type.VOID_TYPE))
680 758 {
681   - Type[] args = Type.getArgumentTypes(method.desc);
682 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 842 * @param method
702 843 * @param args
703 844 */
704   - private void invokeInterfaceMethod(MethodNode method, Type[] args)
  845 + private int invokeInterfaceMethod(MethodNode method, Type[] args)
705 846 {
706 847 int argNumber = 1;
707 848 for (Type type : args)
... ... @@ -711,6 +852,14 @@ public class HandlerList&lt;T&gt; extends LinkedList&lt;T&gt;
711 852 }
712 853  
713 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 +}
... ...