Fork of Poseidon providing Bukkit #1060 to older Beta versions (b1.0-b1.7.3)
1package org.bukkit.plugin;
2
3import com.google.common.collect.ImmutableSet;
4import com.google.common.collect.MapMaker;
5import com.legacyminecraft.poseidon.Poseidon;
6import com.legacyminecraft.poseidon.event.PoseidonCustomListener;
7import org.bukkit.Server;
8import org.bukkit.command.Command;
9import org.bukkit.command.PluginCommandYamlParser;
10import org.bukkit.command.SimpleCommandMap;
11import org.bukkit.event.Event;
12import org.bukkit.event.Event.Priority;
13import org.bukkit.event.Listener;
14import org.bukkit.permissions.Permissible;
15import org.bukkit.permissions.Permission;
16import org.bukkit.permissions.PermissionDefault;
17import org.bukkit.util.FileUtil;
18
19import java.io.File;
20import java.lang.reflect.Constructor;
21import java.util.*;
22import java.util.logging.Level;
23import java.util.regex.Matcher;
24import java.util.regex.Pattern;
25
26/**
27 * Handles all plugin management from the Server
28 */
29public final class SimplePluginManager implements PluginManager {
30 private final Server server;
31 private final Map<Pattern, PluginLoader> fileAssociations = new HashMap<Pattern, PluginLoader>();
32 private final List<Plugin> plugins = new ArrayList<Plugin>();
33 private final Map<String, Plugin> lookupNames = new HashMap<String, Plugin>();
34 private final Map<Event.Type, SortedSet<RegisteredListener>> listeners = new EnumMap<Event.Type, SortedSet<RegisteredListener>>(Event.Type.class);
35 private static File updateDirectory = null;
36 private final SimpleCommandMap commandMap;
37 private final Map<String, Permission> permissions = new HashMap<String, Permission>();
38 private final Map<Boolean, Set<Permission>> defaultPerms = new LinkedHashMap<Boolean, Set<Permission>>();
39 private final Map<String, Map<Permissible, Boolean>> permSubs = new HashMap<String, Map<Permissible, Boolean>>();
40 private final Map<Boolean, Map<Permissible, Boolean>> defSubs = new HashMap<Boolean, Map<Permissible, Boolean>>();
41 private final Comparator<RegisteredListener> comparer = new Comparator<RegisteredListener>() {
42 public int compare(RegisteredListener i, RegisteredListener j) {
43 int result = i.getPriority().compareTo(j.getPriority());
44
45 if ((result == 0) && (i != j)) {
46 result = 1;
47 }
48
49 return result;
50 }
51 };
52
53 public SimplePluginManager(Server instance, SimpleCommandMap commandMap) {
54 server = instance;
55 this.commandMap = commandMap;
56
57 defaultPerms.put(true, new HashSet<Permission>());
58 defaultPerms.put(false, new HashSet<Permission>());
59 }
60
61 // Project Poseidon Start
62
63 @Override
64 public void registerEvents(Listener listener, Plugin plugin) {
65 if (!plugin.isEnabled()) {
66 throw new IllegalPluginAccessException("Plugin attempted to register " + listener + " while not enabled");
67 } else {
68 for (Map.Entry<Class<? extends Event>, Set<RegisteredListener>> entry : plugin.getPluginLoader().createRegisteredListeners(listener, plugin).entrySet()) {
69 Class<? extends Event> clazz = entry.getKey();
70 Event.Type type = Event.Type.getTypeByName(clazz.getSimpleName().substring(0, clazz.getSimpleName().indexOf("Event")));
71 if (type != null) {
72 getEventListeners(type).addAll(entry.getValue());
73 } else {
74 //If listener implements PoseidonCustomListener, we can be sure it is probably a custom event.
75 if (listener instanceof PoseidonCustomListener) {
76 server.getLogger().log(Level.INFO, plugin.getDescription().getName() + " is utilizing event handlers to receive the custom event " + clazz.getSimpleName() + ". Please be aware this is a hacky beta feature.");
77 getEventListeners(Event.Type.CUSTOM_EVENT).addAll(entry.getValue());
78 } else {
79 String cName = clazz.getName();
80 server.getLogger().log(Level.SEVERE, String.format("Class %s failed to get Event.Type on @EventHandler", cName));
81 }
82 }
83
84 }
85
86 }
87 }
88 // Project Poseidon End
89
90 /**
91 * Registers the specified plugin loader
92 *
93 * @param loader Class name of the PluginLoader to register
94 * @throws IllegalArgumentException Thrown when the given Class is not a valid PluginLoader
95 */
96 public void registerInterface(Class<? extends PluginLoader> loader) throws IllegalArgumentException {
97 PluginLoader instance;
98
99 if (PluginLoader.class.isAssignableFrom(loader)) {
100 Constructor<? extends PluginLoader> constructor;
101
102 try {
103 constructor = loader.getConstructor(Server.class);
104 instance = constructor.newInstance(server);
105 } catch (NoSuchMethodException ex) {
106 String className = loader.getName();
107
108 throw new IllegalArgumentException(String.format("Class %s does not have a public %s(Server) constructor", className, className), ex);
109 } catch (Exception ex) {
110 throw new IllegalArgumentException(String.format("Unexpected exception %s while attempting to construct a new instance of %s", ex.getClass().getName(), loader.getName()), ex);
111 }
112 } else {
113 throw new IllegalArgumentException(String.format("Class %s does not implement interface PluginLoader", loader.getName()));
114 }
115
116 Pattern[] patterns = instance.getPluginFileFilters();
117
118 synchronized (this) {
119 for (Pattern pattern : patterns) {
120 fileAssociations.put(pattern, instance);
121 }
122 }
123 }
124
125 /**
126 * Loads the plugins contained within the specified directory
127 *
128 * @param directory Directory to check for plugins
129 * @return A list of all plugins loaded
130 */
131 public Plugin[] loadPlugins(File directory) {
132 List<Plugin> result = new ArrayList<Plugin>();
133 File[] files = directory.listFiles();
134
135 boolean allFailed = false;
136 boolean finalPass = false;
137
138 LinkedList<File> filesList = new LinkedList(Arrays.asList(files));
139
140 if (!(server.getUpdateFolder().equals(""))) {
141 updateDirectory = new File(directory, server.getUpdateFolder());
142 }
143
144 while (!allFailed || finalPass) {
145 allFailed = true;
146 Iterator<File> itr = filesList.iterator();
147
148 while (itr.hasNext()) {
149 File file = itr.next();
150 Plugin plugin = null;
151
152 try {
153 plugin = loadPlugin(file, finalPass);
154 itr.remove();
155 } catch (UnknownDependencyException ex) {
156 if (finalPass) {
157 server.getLogger().log(Level.SEVERE, "Could not load '" + file.getPath() + "' in folder '" + directory.getPath() + "': " + ex.getMessage(), ex);
158 itr.remove();
159 } else {
160 plugin = null;
161 }
162 } catch (InvalidPluginException ex) {
163 server.getLogger().log(Level.SEVERE, "Could not load '" + file.getPath() + "' in folder '" + directory.getPath() + "': ", ex.getCause());
164 itr.remove();
165 } catch (InvalidDescriptionException ex) {
166 server.getLogger().log(Level.SEVERE, "Could not load '" + file.getPath() + "' in folder '" + directory.getPath() + "': " + ex.getMessage(), ex);
167 itr.remove();
168 }
169
170 if (plugin != null) {
171 result.add(plugin);
172 allFailed = false;
173 finalPass = false;
174 }
175 }
176 if (finalPass) {
177 break;
178 } else if (allFailed) {
179 finalPass = true;
180 }
181 }
182
183 return result.toArray(new Plugin[result.size()]);
184 }
185
186 /**
187 * Loads the plugin in the specified file
188 * <p>
189 * File must be valid according to the current enabled Plugin interfaces
190 *
191 * @param file File containing the plugin to load
192 * @return The Plugin loaded, or null if it was invalid
193 * @throws InvalidPluginException Thrown when the specified file is not a valid plugin
194 * @throws InvalidDescriptionException Thrown when the specified file contains an invalid description
195 */
196 public synchronized Plugin loadPlugin(File file) throws InvalidPluginException, InvalidDescriptionException, UnknownDependencyException {
197 return loadPlugin(file, true);
198 }
199
200 /**
201 * Loads the plugin in the specified file
202 * <p>
203 * File must be valid according to the current enabled Plugin interfaces
204 *
205 * @param file File containing the plugin to load
206 * @param ignoreSoftDependencies Loader will ignore soft dependencies if this flag is set to true
207 * @return The Plugin loaded, or null if it was invalid
208 * @throws InvalidPluginException Thrown when the specified file is not a valid plugin
209 * @throws InvalidDescriptionException Thrown when the specified file contains an invalid description
210 */
211 public synchronized Plugin loadPlugin(File file, boolean ignoreSoftDependencies) throws InvalidPluginException, InvalidDescriptionException, UnknownDependencyException {
212 File updateFile = null;
213
214 if (updateDirectory != null && updateDirectory.isDirectory() && (updateFile = new File(updateDirectory, file.getName())).isFile()) {
215 if (FileUtil.copy(updateFile, file)) {
216 server.getLogger().info("An updated file for \"" + file.getName() + "\" has been found in the update folder. Replacing the original file.");
217 updateFile.delete();
218 }
219 }
220
221 Set<Pattern> filters = fileAssociations.keySet();
222 Plugin result = null;
223
224 for (Pattern filter : filters) {
225 String name = file.getName();
226 Matcher match = filter.matcher(name);
227
228 if (match.find()) {
229 PluginLoader loader = fileAssociations.get(filter);
230
231 result = loader.loadPlugin(file, ignoreSoftDependencies);
232 }
233 }
234
235 if (result != null) {
236 plugins.add(result);
237 lookupNames.put(result.getDescription().getName(), result);
238 }
239
240 return result;
241 }
242
243 /**
244 * Checks if the given plugin is loaded and returns it when applicable
245 * <p>
246 * Please note that the name of the plugin is case-sensitive
247 *
248 * @param name Name of the plugin to check
249 * @return Plugin if it exists, otherwise null
250 */
251 public synchronized Plugin getPlugin(String name) {
252 return lookupNames.get(name);
253 }
254
255 public synchronized Plugin[] getPlugins() {
256 return plugins.toArray(new Plugin[0]);
257 }
258
259 /**
260 * Checks if the given plugin is enabled or not
261 * <p>
262 * Please note that the name of the plugin is case-sensitive.
263 *
264 * @param name Name of the plugin to check
265 * @return true if the plugin is enabled, otherwise false
266 */
267 public boolean isPluginEnabled(String name) {
268 Plugin plugin = getPlugin(name);
269
270 return isPluginEnabled(plugin);
271 }
272
273 /**
274 * Checks if the given plugin is enabled or not
275 *
276 * @param plugin Plugin to check
277 * @return true if the plugin is enabled, otherwise false
278 */
279 public boolean isPluginEnabled(Plugin plugin) {
280 if ((plugin != null) && (plugins.contains(plugin))) {
281 return plugin.isEnabled();
282 } else {
283 return false;
284 }
285 }
286
287 public void enablePlugin(final Plugin plugin) {
288 if (!plugin.isEnabled()) {
289 List<Command> pluginCommands = PluginCommandYamlParser.parse(plugin);
290
291 if (!pluginCommands.isEmpty()) {
292 commandMap.registerAll(plugin.getDescription().getName(), pluginCommands);
293
294 // Project Poseidon - Start - Hide commands
295 for (Command c : pluginCommands) {
296 if (c.isHidden()) {
297 Poseidon.getServer().addHiddenCommand(c.getLabel());
298 Poseidon.getServer().addHiddenCommands(c.getAliases());
299 }
300 }
301 // Project Poseidon - End - Hide commands
302 }
303
304 try {
305 plugin.getPluginLoader().enablePlugin(plugin);
306 } catch (Throwable ex) {
307 server.getLogger().log(Level.SEVERE, "Error occurred (in the plugin loader) while enabling " + plugin.getDescription().getFullName() + " (Is it up to date?): " + ex.getMessage(), ex);
308 }
309 }
310 }
311
312 public void disablePlugins() {
313 for (Plugin plugin : getPlugins()) {
314 disablePlugin(plugin);
315 }
316 }
317
318 public void disablePlugin(final Plugin plugin) {
319 if (plugin.isEnabled()) {
320 try {
321 plugin.getPluginLoader().disablePlugin(plugin);
322 } catch (Throwable ex) {
323 server.getLogger().log(Level.SEVERE, "Error occurred (in the plugin loader) while disabling " + plugin.getDescription().getFullName() + " (Is it up to date?): " + ex.getMessage(), ex);
324 }
325
326 try {
327 server.getScheduler().cancelTasks(plugin);
328 } catch (Throwable ex) {
329 server.getLogger().log(Level.SEVERE, "Error occurred (in the plugin loader) while cancelling tasks for " + plugin.getDescription().getFullName() + " (Is it up to date?): " + ex.getMessage(), ex);
330 }
331
332 try {
333 server.getServicesManager().unregisterAll(plugin);
334 } catch (Throwable ex) {
335 server.getLogger().log(Level.SEVERE, "Error occurred (in the plugin loader) while unregistering services for " + plugin.getDescription().getFullName() + " (Is it up to date?): " + ex.getMessage(), ex);
336 }
337 }
338 }
339
340 public void clearPlugins() {
341 synchronized (this) {
342 disablePlugins();
343 plugins.clear();
344 lookupNames.clear();
345 listeners.clear();
346 fileAssociations.clear();
347 permissions.clear();
348 defaultPerms.get(true).clear();
349 defaultPerms.get(false).clear();
350 }
351 }
352
353 /**
354 * Calls a player related event with the given details
355 *
356 * @param event Event details
357 */
358 public synchronized void callEvent(Event event) {
359 SortedSet<RegisteredListener> eventListeners = listeners.get(event.getType());
360
361 if (eventListeners != null) {
362 for (RegisteredListener registration : eventListeners) {
363 try {
364 registration.callEvent(event);
365 } catch (AuthorNagException ex) {
366 Plugin plugin = registration.getPlugin();
367
368 if (plugin.isNaggable()) {
369 plugin.setNaggable(false);
370
371 String author = "<NoAuthorGiven>";
372
373 if (plugin.getDescription().getAuthors().size() > 0) {
374 author = plugin.getDescription().getAuthors().get(0);
375 }
376 server.getLogger().log(Level.SEVERE, String.format("Nag author: '%s' of '%s' about the following: %s", author, plugin.getDescription().getName(), ex.getMessage()));
377 }
378 } catch (Throwable ex) {
379 server.getLogger().log(Level.SEVERE, "Could not pass event " + event.getType() + " to " + registration.getPlugin().getDescription().getName(), ex);
380 }
381 }
382 }
383 }
384
385 /**
386 * Registers the given event to the specified listener
387 *
388 * @param type EventType to register
389 * @param listener PlayerListener to register
390 * @param priority Priority of this event
391 * @param plugin Plugin to register
392 */
393 public void registerEvent(Event.Type type, Listener listener, Priority priority, Plugin plugin) {
394 if (!plugin.isEnabled()) {
395 throw new IllegalPluginAccessException("Plugin attempted to register " + type + " while not enabled");
396 }
397
398 getEventListeners(type).add(new RegisteredListener(listener, priority, plugin, type));
399 }
400
401 /**
402 * Registers the given event to the specified listener using a directly passed EventExecutor
403 *
404 * @param type EventType to register
405 * @param listener PlayerListener to register
406 * @param executor EventExecutor to register
407 * @param priority Priority of this event
408 * @param plugin Plugin to register
409 */
410 public void registerEvent(Event.Type type, Listener listener, EventExecutor executor, Priority priority, Plugin plugin) {
411 if (!plugin.isEnabled()) {
412 throw new IllegalPluginAccessException("Plugin attempted to register " + type + " while not enabled");
413 }
414
415 getEventListeners(type).add(new RegisteredListener(listener, executor, priority, plugin));
416 }
417
418 /**
419 * Returns a SortedSet of RegisteredListener for the specified event type creating a new queue if needed
420 *
421 * @param type EventType to lookup
422 * @return SortedSet<RegisteredListener> the looked up or create queue matching the requested type
423 */
424 private SortedSet<RegisteredListener> getEventListeners(Event.Type type) {
425 SortedSet<RegisteredListener> eventListeners = listeners.get(type);
426
427 if (eventListeners != null) {
428 return eventListeners;
429 }
430
431 eventListeners = new TreeSet<RegisteredListener>(comparer);
432 listeners.put(type, eventListeners);
433 return eventListeners;
434 }
435
436 public Permission getPermission(String name) {
437 return permissions.get(name.toLowerCase());
438 }
439
440 public void addPermission(Permission perm) {
441 String name = perm.getName().toLowerCase();
442
443 if (permissions.containsKey(name)) {
444 throw new IllegalArgumentException("The permission " + name + " is already defined!");
445 }
446
447 permissions.put(name, perm);
448 calculatePermissionDefault(perm);
449 }
450
451 public Set<Permission> getDefaultPermissions(boolean op) {
452 return ImmutableSet.copyOf(defaultPerms.get(op));
453 }
454
455 public void removePermission(Permission perm) {
456 removePermission(perm.getName().toLowerCase());
457 }
458
459 public void removePermission(String name) {
460 permissions.remove(name);
461 }
462
463 public void recalculatePermissionDefaults(Permission perm) {
464 if (permissions.containsValue(perm)) {
465 defaultPerms.get(true).remove(perm);
466 defaultPerms.get(false).remove(perm);
467
468 calculatePermissionDefault(perm);
469 }
470 }
471
472 private void calculatePermissionDefault(Permission perm) {
473 if ((perm.getDefault() == PermissionDefault.OP) || (perm.getDefault() == PermissionDefault.TRUE)) {
474 defaultPerms.get(true).add(perm);
475 dirtyPermissibles(true);
476 }
477 if ((perm.getDefault() == PermissionDefault.NOT_OP) || (perm.getDefault() == PermissionDefault.TRUE)) {
478 defaultPerms.get(false).add(perm);
479 dirtyPermissibles(false);
480 }
481 }
482
483 private void dirtyPermissibles(boolean op) {
484 Set<Permissible> permissibles = getDefaultPermSubscriptions(op);
485
486 for (Permissible p : permissibles) {
487 p.recalculatePermissions();
488 }
489 }
490
491 public void subscribeToPermission(String permission, Permissible permissible) {
492 String name = permission.toLowerCase();
493 Map<Permissible, Boolean> map = permSubs.get(name);
494
495 if (map == null) {
496 map = new MapMaker().weakKeys().makeMap();
497 permSubs.put(name, map);
498 }
499
500 map.put(permissible, true);
501 }
502
503 public void unsubscribeFromPermission(String permission, Permissible permissible) {
504 String name = permission.toLowerCase();
505 Map<Permissible, Boolean> map = permSubs.get(name);
506
507 if (map != null) {
508 map.remove(permissible);
509
510 if (map.isEmpty()) {
511 permSubs.remove(name);
512 }
513 }
514 }
515
516 public Set<Permissible> getPermissionSubscriptions(String permission) {
517 String name = permission.toLowerCase();
518 Map<Permissible, Boolean> map = permSubs.get(name);
519
520 if (map == null) {
521 return ImmutableSet.of();
522 } else {
523 return ImmutableSet.copyOf(map.keySet());
524 }
525 }
526
527 public void subscribeToDefaultPerms(boolean op, Permissible permissible) {
528 Map<Permissible, Boolean> map = defSubs.get(op);
529
530 if (map == null) {
531 map = new MapMaker().weakKeys().makeMap();
532 defSubs.put(op, map);
533 }
534
535 map.put(permissible, true);
536 }
537
538 public void unsubscribeFromDefaultPerms(boolean op, Permissible permissible) {
539 Map<Permissible, Boolean> map = defSubs.get(op);
540
541 if (map != null) {
542 map.remove(permissible);
543
544 if (map.isEmpty()) {
545 defSubs.remove(op);
546 }
547 }
548 }
549
550 public Set<Permissible> getDefaultPermSubscriptions(boolean op) {
551 Map<Permissible, Boolean> map = defSubs.get(op);
552
553 if (map == null) {
554 return ImmutableSet.of();
555 } else {
556 return ImmutableSet.copyOf(map.keySet());
557 }
558 }
559
560 public Set<Permission> getPermissions() {
561 return new HashSet<Permission>(permissions.values());
562 }
563}