summaryrefslogtreecommitdiff
path: root/dicore3/command/src/main/java/io/dico/dicore/command/registration/reflect/ReflectiveCallFlags.java
blob: fa198c2134c6465fdb78eec79e0fa637bdd44b66 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
package io.dico.dicore.command.registration.reflect;

import io.dico.dicore.command.CommandException;
import io.dico.dicore.command.ExecutionContext;
import io.dico.dicore.exceptions.checkedfunctions.CheckedSupplier;

/**
 * Call flags store which extra parameters the target function expects on top of command parameters.
 * All 4 possible extra parameters are listed below.
 * <p>
 * Extra parameters are ordered by the bit that represents them in the call flags.
 * They can either be leading or trailing the command's parameters.
 */
public class ReflectiveCallFlags {

    /**
     * Receiver ({@code this} in some kotlin functions - always first parameter)
     *
     * @see ICommandInterceptor#getReceiver(io.dico.dicore.command.ExecutionContext, java.lang.reflect.Method, String)
     */
    public static final int RECEIVER_BIT = 1 << 0;

    /**
     * CommandSender
     *
     * @see org.bukkit.command.CommandSender
     */
    public static final int SENDER_BIT = 1 << 1;

    /**
     * ExecutionContext
     *
     * @see io.dico.dicore.command.ExecutionContext
     */
    public static final int CONTEXT_BIT = 1 << 2;

    /**
     * Continuation (trailing parameters of kotlin suspended functions)
     *
     * @see kotlin.coroutines.Continuation
     */
    public static final int CONTINUATION_BIT = 1 << 3;

    /**
     * Mask of extra parameters that trail the command's parameters, instead of leading.
     */
    public static final int TRAILING_MASK = CONTINUATION_BIT;

    /**
     * Check if the call arg is trailing the command's parameters.
     *
     * @param bit the bit used for the call flag
     * @return true if the call arg is trailing the command's parameters
     */
    public static boolean isTrailingCallArg(int bit) {
        return (bit & TRAILING_MASK) != 0;
    }

    /**
     * Number of call arguments leading the command parameters.
     *
     * @param flags the call flags
     * @return the number of call arguments leading the command parameters
     */
    public static int getLeadingCallArgNum(int flags) {
        return Integer.bitCount(flags & ~TRAILING_MASK);
    }

    /**
     * Number of call arguments trailing the command parameters.
     *
     * @param flags the call flags
     * @return the number of call arguments trailing the command parameters
     */
    public static int getTrailingCallArgNum(int flags) {
        return Integer.bitCount(flags & TRAILING_MASK);
    }

    /**
     * Check if the flags contain the call arg.
     *
     * @param flags the call flags
     * @param bit   the bit used for the call flag
     * @return true if the flags contain the call arg
     */
    public static boolean hasCallArg(int flags, int bit) {
        return (flags & bit) != 0;
    }

    /**
     * Get the index used for the call arg when calling the reflective function
     *
     * @param flags           the call flags
     * @param bit             the bit used for the call flag
     * @param cmdParameterNum the number of parameters of the command
     * @return the index used for the call arg
     */
    public static int getCallArgIndex(int flags, int bit, int cmdParameterNum) {
        if ((bit & TRAILING_MASK) == 0) {
            // Leading.

            int preceding = precedingMaskFrom(bit);
            int mask = flags & precedingMaskFrom(bit) & ~TRAILING_MASK;

            // Count the number of present call args that are leading and precede the given bit
            return Integer.bitCount(flags & precedingMaskFrom(bit) & ~TRAILING_MASK);
        } else {
            // Trailing.

            // Count the number of present call args that are leading
            // plus the number of present call args that are trailing and precede the given bit
            // plus the command's parameters

            return Integer.bitCount(flags & ~TRAILING_MASK)
                + Integer.bitCount(flags & precedingMaskFrom(bit) & TRAILING_MASK)
                + cmdParameterNum;
        }
    }

    /**
     * Get the mask for all bits trailing the given fromBit
     *
     * <p>
     * For example, if the bit is 00010000
     * This function returns 00001111
     * <p>
     *
     * @param fromBit number with the bit set there the ones should stop.
     * @return the mask for all bits trailing the given fromBit
     */
    private static int precedingMaskFrom(int fromBit) {
        int trailingZeros = Integer.numberOfTrailingZeros(fromBit);
        if (trailingZeros == 0) return 0;
        return -1 >>> -trailingZeros;
    }

    /**
     * Get the object array used to call the function.
     *
     * @param callFlags        the call flags
     * @param context          the context
     * @param parameterOrder   the order of parameters in the function
     * @param receiverFunction the function that will create the receiver for this call, if applicable
     * @return the call args
     */
    public static Object[] getCallArgs(
        int callFlags,
        ExecutionContext context,
        String[] parameterOrder,
        CheckedSupplier<Object, CommandException> receiverFunction
    ) throws CommandException {
        int leadingParameterNum = getLeadingCallArgNum(callFlags);
        int cmdParameterNum = parameterOrder.length;
        int trailingParameterNum = getTrailingCallArgNum(callFlags);

        Object[] result = new Object[leadingParameterNum + cmdParameterNum + trailingParameterNum];

        if (hasCallArg(callFlags, RECEIVER_BIT)) {
            int index = getCallArgIndex(callFlags, RECEIVER_BIT, cmdParameterNum);
            result[index] = receiverFunction.get();
        }

        if (hasCallArg(callFlags, SENDER_BIT)) {
            int index = getCallArgIndex(callFlags, SENDER_BIT, cmdParameterNum);
            result[index] = context.getSender();
        }

        if (hasCallArg(callFlags, CONTEXT_BIT)) {
            int index = getCallArgIndex(callFlags, CONTEXT_BIT, cmdParameterNum);
            result[index] = context;
        }

        if (hasCallArg(callFlags, CONTINUATION_BIT)) {
            int index = getCallArgIndex(callFlags, CONTINUATION_BIT, cmdParameterNum);
            result[index] = null; // filled in later.
        }

        for (int i = 0; i < parameterOrder.length; i++) {
            String parameterName = parameterOrder[i];
            result[leadingParameterNum + i] = context.get(parameterName);
        }

        return result;
    }

}