-
Notifications
You must be signed in to change notification settings - Fork 18
Expand file tree
/
Copy pathPythonHooks.java
More file actions
385 lines (345 loc) · 12.8 KB
/
PythonHooks.java
File metadata and controls
385 lines (345 loc) · 12.8 KB
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
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
/**
*
*/
package net.lahwran.bukkit.jython;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import org.bukkit.Bukkit;
import org.bukkit.event.Event;
import org.bukkit.event.EventPriority;
import org.bukkit.event.HandlerList;
import org.bukkit.plugin.PluginDescriptionFile;
import org.bukkit.plugin.PluginManager;
import org.python.core.Py;
import org.python.core.PyBoolean;
import org.python.core.PyBuiltinClassMethodNarrow;
import org.python.core.PyBuiltinMethod;
import org.python.core.PyClassMethod;
import org.python.core.PyException;
import org.python.core.PyFunction;
import org.python.core.PyList;
import org.python.core.PyObject;
import org.python.core.PyString;
import org.python.core.PyTuple;
import org.python.core.PyType;
import com.master.bukkit.python.ReflectionHelper;
/**
* Python decorators and handler registration
* @author lahwran
*/
public class PythonHooks {
/**
* Once set, no more hooks can be added
*/
private boolean frozen = false;
/**
* Function to call when plugin is being enabled
*/
PyFunction onEnable;
/**
* Function to call when plugin is being disabled
*/
PyFunction onDisable;
/**
* Function to call when plugin is initialized
*/
PyFunction onLoaded;
/**
* List of handlers to register
*/
ArrayList<PythonEventHandler> eventhandlers = new ArrayList<PythonEventHandler>();
/**
* List of handlers to register
*/
ArrayList<PythonCommandHandler> commandhandlers = new ArrayList<PythonCommandHandler>();
/**
* List of custom events
*/
Map<String, Class<? extends Event>> customEvents = new HashMap<String, Class<? extends Event>>();
/**
* Plugin description to modify when registering commands
*/
PluginDescriptionFile plugindesc;
/**
* @param plugindesc Plugin description to modify when registering commands
*/
PythonHooks(PluginDescriptionFile plugindesc) {
this.plugindesc = plugindesc;
}
@SuppressWarnings("unchecked")
private void addCommandInfo(String name, String usage, String desc, List aliases) {
Object object = plugindesc.getCommands();
if (object == null) {
object = new HashMap<String, HashMap<String, Object>>();
try {
ReflectionHelper.setPrivateValue(plugindesc, "commands", object);
} catch (Throwable t) {
t.printStackTrace();
throw new PyException(new PyString("error"), new PyString("Plugin commands list does not exist"));
}
}
Map<String, Map<String, Object>> map = (Map<String, Map<String, Object>>) object;
if (map.containsKey(name)) {
if (desc != null || usage != null || aliases != null)
throw new PyException(new PyString("error"), new PyString("Plugin already has a command called '"+name+"'"));
else
return;
}
Map<String, Object> commandmap = new HashMap<String, Object>();
if (desc != null)
commandmap.put("description", desc);
if (usage != null)
commandmap.put("usage", usage);
if (aliases != null)
commandmap.put("aliases", aliases);
map.put(name, commandmap);
}
/**
* Register everything with bukkit
* @param plugin plugin to do registration as
*/
void doRegistrations(PythonPlugin plugin) {
frozen = true;
PluginManager pm = plugin.getServer().getPluginManager();
for (int i=0; i<eventhandlers.size(); i++) {
eventhandlers.get(i).register(pm, plugin);
}
for (int i=0; i<commandhandlers.size(); i++) {
commandhandlers.get(i).register(plugin);
}
}
/**
* Check if we're allowed to register stuff, and if not, throw an exception
*/
private void checkFrozen() {
if (frozen)
throw new PyException(new PyString("error"), new PyString("Cannot register handlers when frozen"));
}
public void registerEvent(PyObject handler, Class<? extends Event> type, EventPriority priority) {
checkFrozen();
PythonEventHandler wrapper = new PythonEventHandler(handler, type, priority);
eventhandlers.add(wrapper);
}
/**
* Register an event. python-facing. this version converts from string info.
* @param handler function handler
* @param type Event type string
* @param priority Event priority string
*/
public void registerEvent(PyObject handler, PyString type, PyString priority) {
try {
String clazz = type.asString();
Class<?> event = null;
if(clazz.contains(".")) {
try {
//try if we can find the class
event = Class.forName(clazz);
} catch (ClassNotFoundException e) {
//assume the subpackage and class name was given and append org.bukkit.event
event = Class.forName("org.bukkit.event." + clazz);
}
}
else if(customEvents.containsKey(clazz)) {
//check if the name of a custom event was given
event = customEvents.get(clazz);
}
else
{
throw new IllegalArgumentException("Could not find Event " + clazz);
}
if(!event.getClass().isInstance(event)) {
throw new IllegalArgumentException(type.asString().toUpperCase() + " is not of type Event");
}
Class<? extends Event> realtype = (Class<? extends Event>)event;
EventPriority realpriority = EventPriority.valueOf(priority.upper());
registerEvent(handler, realtype, realpriority);
} catch (ClassNotFoundException e) {
Bukkit.getLogger().log(Level.SEVERE, "Could not register event " + type +" because the event could not be found", e);
}
}
/**
* Register a command with no extra metadata
* @param func function to set as handler
* @param name name to register
*/
public void registerCommand(PyObject func, String name) {
registerCommand(func, name, null, null, null, null);
}
/**
* Register a command with no extra metadata
* @param func function to set as handler; function's name is used as command name
*/
public void registerCommand(PyObject func) {
registerCommand(func, null);
}
/**
* Register a command with no extra metadata
* @param func function to set as handler
* @param name name to use
* @param usage metadata
* @param desc metadata
* @param aliases metadata
*/
public void registerCommand(PyObject func, String name, String usage, String desc, List<?> aliases, PyObject tabComplete) {
checkFrozen();
String finalname = name;
if (finalname == null)
finalname = ((PyFunction)func).__name__;
addCommandInfo(finalname, usage, desc, aliases);
PythonCommandHandler handler = new PythonCommandHandler(func, finalname, tabComplete);
commandhandlers.add(handler);
}
/**
* Python decorator. functions decorated with this are called on enable
* <pre>
* @hook.enable
* def enable():
* print "enabled!"
* </pre>
* @param func function to decorate
* @return decorated function
*/
public PyFunction enable(PyFunction func) {
onEnable = func;
return func;
}
/**
* Python decorator. functions decorated with this are called on disable
* <pre>
* @hook.disable
* def disable():
* print "enabled!"
* </pre>
* @param func function to decorate
* @return decorated function
*/
public PyFunction disable(PyFunction func) {
onDisable = func;
return func;
}
/**
* Python decorator. functions decorated with this are called on load complete
* <pre>
* @hook.load_complete
* def loaded():
* print "load complete!"
* </pre>
* @param func function to decorate
* @return decorated function
*/
public PyFunction load_complete(PyFunction func) {
onLoaded = func;
return func;
}
/**
* Python decorator. functions decorated with this are called as event handlers
* @param type event type
* @param priority event priority
* @return decorated function
*/
public PyObject event(final PyString type, final PyString priority) {
return new PyObject() {
public PyObject __call__(PyObject func) {
registerEvent(func, type, priority);
return func;
}
};
}
/**
* Python decorator. functions decorated with this are called as event handlers. uses normal priority.
* @param type event type
* @return decorated function
*/
public PyObject event(final PyString type) {
return new PyObject() {
public PyObject __call__(PyObject func) {
registerEvent(func, type, new PyString("Normal"));
return func;
}
};
}
/**
* command decorator. approximately equivalent python:
* <pre>
* def command(arg1, desc=None, usage=None, aliases=None, tabComplete=None):
* if isfunc(arg1):
* registerFunc(arg1, arg1.func_name)
* else:
* def decorate(func):
* registerFunc(func, arg1 if arg1 else func.func_name, usage, desc, aliases)
* </pre>
* the literally equivalent python looks so similar to the actual code as to not be worth mentioning
* @param args jython magic
* @param keywords jython magic
* @return jython magic
*/
public PyObject command(PyObject args[], String keywords[]) {
int kwdelta = args.length - keywords.length;
if (args.length == 1 && args[0].isCallable()) {
registerCommand((PyFunction) args[0]);
return args[0];
} else if (kwdelta == 1 || kwdelta == 0) {
String desc = null;
String usage = null;
List<?> aliases = null;
PyObject tabComplete = null;
for (int i = kwdelta; i < args.length; i++) {
String keyword = keywords[i - kwdelta];
if (keyword.equals("desc") || keyword.equals("description"))
desc = args[i].toString();
else if (keyword.equals("usage"))
usage = args[i].toString();
else if (keyword.equals("aliases"))
aliases = new PyList(args[i]);
else if (keyword.equals("onTabComplete"))
tabComplete = args[i];
}
final String name;
if (kwdelta == 1)
name = args[0].toString();
else
name = null;
final String finaldesc = desc;
final String finalusage = usage;
final List<?> finalaliases = aliases;
final PyObject finaltabcomplete = tabComplete;
return new PyObject() {
public PyObject __call__(PyObject func) {
registerCommand(func, name, finalusage, finaldesc, finalaliases, finaltabcomplete);
return func;
}
};
} else {
throw Py.TypeError("you gave command() bad arguments, but lahwran was tired when he wrote this, so you don't get a helpful error message. sucks to be you.");
}
}
public PyObject custom_event(PyType event) {
Class<?> proxy = event.getProxyType();
if(Event.class.isAssignableFrom(proxy)) {
//add the stupid handler list attribute and get handler list method
//which the bukkit team could not add themselves for some strange reason
// event.__setattr__("handlerList", Py.java2py(new HandlerList()));
// ((PyType)event.getBase()).addMethod(new PyBuiltinClassMethodNarrow("getHandlerList", 0, 0) {
// @Override
// public PyObject __call__() {
// return self.__getattr__("handlerList");
// }
// });
// event.addMethod(new PyBuiltinClassMethodNarrow("getHandlers", 0, 0) {
// @Override
// public PyObject __call__() {
// return self.__getattr__("handlerList");
// }
// });
customEvents.put(event.getName(), (Class<? extends Event>) proxy);
}
else {
throw new IllegalArgumentException("Tried to register a custom Event which does not extend Event");
}
return event;
}
}