Bukkit  1.4.7-R1.0
 All Classes Namespaces Files Functions Variables Enumerator Pages
SimplePluginManager.java
Go to the documentation of this file.
1 package org.bukkit.plugin;
2 
3 import java.io.File;
4 import java.lang.reflect.Constructor;
5 import java.lang.reflect.Method;
6 import java.util.ArrayList;
7 import java.util.Collection;
8 import java.util.HashMap;
9 import java.util.HashSet;
10 import java.util.Iterator;
11 import java.util.LinkedHashMap;
12 import java.util.LinkedList;
13 import java.util.List;
14 import java.util.Map;
15 import java.util.Set;
16 import java.util.WeakHashMap;
17 import java.util.logging.Level;
18 import java.util.regex.Matcher;
19 import java.util.regex.Pattern;
20 
21 import org.apache.commons.lang.Validate;
22 import org.bukkit.Server;
23 import org.bukkit.command.Command;
24 import org.bukkit.command.PluginCommandYamlParser;
25 import org.bukkit.command.SimpleCommandMap;
26 import org.bukkit.event.Event;
27 import org.bukkit.event.EventPriority;
28 import org.bukkit.event.HandlerList;
29 import org.bukkit.event.Listener;
30 import org.bukkit.permissions.Permissible;
31 import org.bukkit.permissions.Permission;
32 import org.bukkit.permissions.PermissionDefault;
33 import org.bukkit.util.FileUtil;
34 
35 import com.google.common.collect.ImmutableSet;
36 
40 public final class SimplePluginManager implements PluginManager {
41  private final Server server;
42  private final Map<Pattern, PluginLoader> fileAssociations = new HashMap<Pattern, PluginLoader>();
43  private final List<Plugin> plugins = new ArrayList<Plugin>();
44  private final Map<String, Plugin> lookupNames = new HashMap<String, Plugin>();
45  private static File updateDirectory = null;
46  private final SimpleCommandMap commandMap;
47  private final Map<String, Permission> permissions = new HashMap<String, Permission>();
48  private final Map<Boolean, Set<Permission>> defaultPerms = new LinkedHashMap<Boolean, Set<Permission>>();
49  private final Map<String, Map<Permissible, Boolean>> permSubs = new HashMap<String, Map<Permissible, Boolean>>();
50  private final Map<Boolean, Map<Permissible, Boolean>> defSubs = new HashMap<Boolean, Map<Permissible, Boolean>>();
51  private boolean useTimings = false;
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 
67  public void registerInterface(Class<? extends PluginLoader> loader) throws IllegalArgumentException {
68  PluginLoader instance;
69 
70  if (PluginLoader.class.isAssignableFrom(loader)) {
71  Constructor<? extends PluginLoader> constructor;
72 
73  try {
74  constructor = loader.getConstructor(Server.class);
75  instance = constructor.newInstance(server);
76  } catch (NoSuchMethodException ex) {
77  String className = loader.getName();
78 
79  throw new IllegalArgumentException(String.format("Class %s does not have a public %s(Server) constructor", className, className), ex);
80  } catch (Exception ex) {
81  throw new IllegalArgumentException(String.format("Unexpected exception %s while attempting to construct a new instance of %s", ex.getClass().getName(), loader.getName()), ex);
82  }
83  } else {
84  throw new IllegalArgumentException(String.format("Class %s does not implement interface PluginLoader", loader.getName()));
85  }
86 
87  Pattern[] patterns = instance.getPluginFileFilters();
88 
89  synchronized (this) {
90  for (Pattern pattern : patterns) {
91  fileAssociations.put(pattern, instance);
92  }
93  }
94  }
95 
102  public Plugin[] loadPlugins(File directory) {
103  Validate.notNull(directory, "Directory cannot be null");
104  Validate.isTrue(directory.isDirectory(), "Directory must be a directory");
105 
106  List<Plugin> result = new ArrayList<Plugin>();
107  Set<Pattern> filters = fileAssociations.keySet();
108 
109  if (!(server.getUpdateFolder().equals(""))) {
110  updateDirectory = new File(directory, server.getUpdateFolder());
111  }
112 
113  Map<String, File> plugins = new HashMap<String, File>();
114  Set<String> loadedPlugins = new HashSet<String>();
115  Map<String, Collection<String>> dependencies = new HashMap<String, Collection<String>>();
116  Map<String, Collection<String>> softDependencies = new HashMap<String, Collection<String>>();
117 
118  // This is where it figures out all possible plugins
119  for (File file : directory.listFiles()) {
120  PluginLoader loader = null;
121  for (Pattern filter : filters) {
122  Matcher match = filter.matcher(file.getName());
123  if (match.find()) {
124  loader = fileAssociations.get(filter);
125  }
126  }
127 
128  if (loader == null) continue;
129 
130  PluginDescriptionFile description = null;
131  try {
132  description = loader.getPluginDescription(file);
133  } catch (InvalidDescriptionException ex) {
134  server.getLogger().log(Level.SEVERE, "Could not load '" + file.getPath() + "' in folder '" + directory.getPath() + "'", ex);
135  continue;
136  }
137 
138  plugins.put(description.getName(), file);
139 
140  Collection<String> softDependencySet = description.getSoftDepend();
141  if (softDependencySet != null) {
142  if (softDependencies.containsKey(description.getName())) {
143  // Duplicates do not matter, they will be removed together if applicable
144  softDependencies.get(description.getName()).addAll(softDependencySet);
145  } else {
146  softDependencies.put(description.getName(), new LinkedList<String>(softDependencySet));
147  }
148  }
149 
150  Collection<String> dependencySet = description.getDepend();
151  if (dependencySet != null) {
152  dependencies.put(description.getName(), new LinkedList<String>(dependencySet));
153  }
154 
155  Collection<String> loadBeforeSet = description.getLoadBefore();
156  if (loadBeforeSet != null) {
157  for (String loadBeforeTarget : loadBeforeSet) {
158  if (softDependencies.containsKey(loadBeforeTarget)) {
159  softDependencies.get(loadBeforeTarget).add(description.getName());
160  } else {
161  // softDependencies is never iterated, so 'ghost' plugins aren't an issue
162  Collection<String> shortSoftDependency = new LinkedList<String>();
163  shortSoftDependency.add(description.getName());
164  softDependencies.put(loadBeforeTarget, shortSoftDependency);
165  }
166  }
167  }
168  }
169 
170  while (!plugins.isEmpty()) {
171  boolean missingDependency = true;
172  Iterator<String> pluginIterator = plugins.keySet().iterator();
173 
174  while (pluginIterator.hasNext()) {
175  String plugin = pluginIterator.next();
176 
177  if (dependencies.containsKey(plugin)) {
178  Iterator<String> dependencyIterator = dependencies.get(plugin).iterator();
179 
180  while (dependencyIterator.hasNext()) {
181  String dependency = dependencyIterator.next();
182 
183  // Dependency loaded
184  if (loadedPlugins.contains(dependency)) {
185  dependencyIterator.remove();
186 
187  // We have a dependency not found
188  } else if (!plugins.containsKey(dependency)) {
189  missingDependency = false;
190  File file = plugins.get(plugin);
191  pluginIterator.remove();
192  softDependencies.remove(plugin);
193  dependencies.remove(plugin);
194 
195  server.getLogger().log(
196  Level.SEVERE,
197  "Could not load '" + file.getPath() + "' in folder '" + directory.getPath() + "'",
198  new UnknownDependencyException(dependency));
199  break;
200  }
201  }
202 
203  if (dependencies.containsKey(plugin) && dependencies.get(plugin).isEmpty()) {
204  dependencies.remove(plugin);
205  }
206  }
207  if (softDependencies.containsKey(plugin)) {
208  Iterator<String> softDependencyIterator = softDependencies.get(plugin).iterator();
209 
210  while (softDependencyIterator.hasNext()) {
211  String softDependency = softDependencyIterator.next();
212 
213  // Soft depend is no longer around
214  if (!plugins.containsKey(softDependency)) {
215  softDependencyIterator.remove();
216  }
217  }
218 
219  if (softDependencies.get(plugin).isEmpty()) {
220  softDependencies.remove(plugin);
221  }
222  }
223  if (!(dependencies.containsKey(plugin) || softDependencies.containsKey(plugin)) && plugins.containsKey(plugin)) {
224  // We're clear to load, no more soft or hard dependencies left
225  File file = plugins.get(plugin);
226  pluginIterator.remove();
227  missingDependency = false;
228 
229  try {
230  result.add(loadPlugin(file));
231  loadedPlugins.add(plugin);
232  continue;
233  } catch (InvalidPluginException ex) {
234  server.getLogger().log(Level.SEVERE, "Could not load '" + file.getPath() + "' in folder '" + directory.getPath() + "'", ex);
235  }
236  }
237  }
238 
239  if (missingDependency) {
240  // We now iterate over plugins until something loads
241  // This loop will ignore soft dependencies
242  pluginIterator = plugins.keySet().iterator();
243 
244  while (pluginIterator.hasNext()) {
245  String plugin = pluginIterator.next();
246 
247  if (!dependencies.containsKey(plugin)) {
248  softDependencies.remove(plugin);
249  missingDependency = false;
250  File file = plugins.get(plugin);
251  pluginIterator.remove();
252 
253  try {
254  result.add(loadPlugin(file));
255  loadedPlugins.add(plugin);
256  break;
257  } catch (InvalidPluginException ex) {
258  server.getLogger().log(Level.SEVERE, "Could not load '" + file.getPath() + "' in folder '" + directory.getPath() + "'", ex);
259  }
260  }
261  }
262  // We have no plugins left without a depend
263  if (missingDependency) {
264  softDependencies.clear();
265  dependencies.clear();
266  Iterator<File> failedPluginIterator = plugins.values().iterator();
267 
268  while (failedPluginIterator.hasNext()) {
269  File file = failedPluginIterator.next();
270  failedPluginIterator.remove();
271  server.getLogger().log(Level.SEVERE, "Could not load '" + file.getPath() + "' in folder '" + directory.getPath() + "': circular dependency detected");
272  }
273  }
274  }
275  }
276 
277  return result.toArray(new Plugin[result.size()]);
278  }
279 
290  public synchronized Plugin loadPlugin(File file) throws InvalidPluginException, UnknownDependencyException {
291  Validate.notNull(file, "File cannot be null");
292 
293  checkUpdate(file);
294 
295  Set<Pattern> filters = fileAssociations.keySet();
296  Plugin result = null;
297 
298  for (Pattern filter : filters) {
299  String name = file.getName();
300  Matcher match = filter.matcher(name);
301 
302  if (match.find()) {
303  PluginLoader loader = fileAssociations.get(filter);
304 
305  result = loader.loadPlugin(file);
306  }
307  }
308 
309  if (result != null) {
310  plugins.add(result);
311  lookupNames.put(result.getDescription().getName(), result);
312  }
313 
314  return result;
315  }
316 
317  private void checkUpdate(File file) {
318  if (updateDirectory == null || !updateDirectory.isDirectory()) {
319  return;
320  }
321 
322  File updateFile = new File(updateDirectory, file.getName());
323  if (updateFile.isFile() && FileUtil.copy(updateFile, file)) {
324  updateFile.delete();
325  }
326  }
327 
336  public synchronized Plugin getPlugin(String name) {
337  return lookupNames.get(name);
338  }
339 
340  public synchronized Plugin[] getPlugins() {
341  return plugins.toArray(new Plugin[0]);
342  }
343 
352  public boolean isPluginEnabled(String name) {
353  Plugin plugin = getPlugin(name);
354 
355  return isPluginEnabled(plugin);
356  }
357 
364  public boolean isPluginEnabled(Plugin plugin) {
365  if ((plugin != null) && (plugins.contains(plugin))) {
366  return plugin.isEnabled();
367  } else {
368  return false;
369  }
370  }
371 
372  public void enablePlugin(final Plugin plugin) {
373  if (!plugin.isEnabled()) {
374  List<Command> pluginCommands = PluginCommandYamlParser.parse(plugin);
375 
376  if (!pluginCommands.isEmpty()) {
377  commandMap.registerAll(plugin.getDescription().getName(), pluginCommands);
378  }
379 
380  try {
381  plugin.getPluginLoader().enablePlugin(plugin);
382  } catch (Throwable ex) {
383  server.getLogger().log(Level.SEVERE, "Error occurred (in the plugin loader) while enabling " + plugin.getDescription().getFullName() + " (Is it up to date?)", ex);
384  }
385 
387  }
388  }
389 
390  public void disablePlugins() {
391  Plugin[] plugins = getPlugins();
392  for (int i = plugins.length - 1; i >= 0; i--) {
393  disablePlugin(plugins[i]);
394  }
395  }
396 
397  public void disablePlugin(final Plugin plugin) {
398  if (plugin.isEnabled()) {
399  try {
400  plugin.getPluginLoader().disablePlugin(plugin);
401  } catch (Throwable ex) {
402  server.getLogger().log(Level.SEVERE, "Error occurred (in the plugin loader) while disabling " + plugin.getDescription().getFullName() + " (Is it up to date?)", ex);
403  }
404 
405  try {
406  server.getScheduler().cancelTasks(plugin);
407  } catch (Throwable ex) {
408  server.getLogger().log(Level.SEVERE, "Error occurred (in the plugin loader) while cancelling tasks for " + plugin.getDescription().getFullName() + " (Is it up to date?)", ex);
409  }
410 
411  try {
412  server.getServicesManager().unregisterAll(plugin);
413  } catch (Throwable ex) {
414  server.getLogger().log(Level.SEVERE, "Error occurred (in the plugin loader) while unregistering services for " + plugin.getDescription().getFullName() + " (Is it up to date?)", ex);
415  }
416 
417  try {
418  HandlerList.unregisterAll(plugin);
419  } catch (Throwable ex) {
420  server.getLogger().log(Level.SEVERE, "Error occurred (in the plugin loader) while unregistering events for " + plugin.getDescription().getFullName() + " (Is it up to date?)", ex);
421  }
422 
423  try {
426  } catch(Throwable ex) {
427  server.getLogger().log(Level.SEVERE, "Error occurred (in the plugin loader) while unregistering plugin channels for " + plugin.getDescription().getFullName() + " (Is it up to date?)", ex);
428  }
429  }
430  }
431 
432  public void clearPlugins() {
433  synchronized (this) {
434  disablePlugins();
435  plugins.clear();
436  lookupNames.clear();
438  fileAssociations.clear();
439  permissions.clear();
440  defaultPerms.get(true).clear();
441  defaultPerms.get(false).clear();
442  }
443  }
444 
451  public void callEvent(Event event) {
452  if (event.isAsynchronous()) {
453  if (Thread.holdsLock(this)) {
454  throw new IllegalStateException(event.getEventName() + " cannot be triggered asynchronously from inside synchronized code.");
455  }
456  if (server.isPrimaryThread()) {
457  throw new IllegalStateException(event.getEventName() + " cannot be triggered asynchronously from primary server thread.");
458  }
459  fireEvent(event);
460  } else {
461  synchronized (this) {
462  fireEvent(event);
463  }
464  }
465  }
466 
467  private void fireEvent(Event event) {
468  HandlerList handlers = event.getHandlers();
469  RegisteredListener[] listeners = handlers.getRegisteredListeners();
470 
471  for (RegisteredListener registration : listeners) {
472  if (!registration.getPlugin().isEnabled()) {
473  continue;
474  }
475 
476  try {
477  registration.callEvent(event);
478  } catch (AuthorNagException ex) {
479  Plugin plugin = registration.getPlugin();
480 
481  if (plugin.isNaggable()) {
482  plugin.setNaggable(false);
483 
484  server.getLogger().log(Level.SEVERE, String.format(
485  "Nag author(s): '%s' of '%s' about the following: %s",
486  plugin.getDescription().getAuthors(),
487  plugin.getDescription().getFullName(),
488  ex.getMessage()
489  ));
490  }
491  } catch (Throwable ex) {
492  server.getLogger().log(Level.SEVERE, "Could not pass event " + event.getEventName() + " to " + registration.getPlugin().getDescription().getFullName(), ex);
493  }
494  }
495  }
496 
497  public void registerEvents(Listener listener, Plugin plugin) {
498  if (!plugin.isEnabled()) {
499  throw new IllegalPluginAccessException("Plugin attempted to register " + listener + " while not enabled");
500  }
501 
502  for (Map.Entry<Class<? extends Event>, Set<RegisteredListener>> entry : plugin.getPluginLoader().createRegisteredListeners(listener, plugin).entrySet()) {
503  getEventListeners(getRegistrationClass(entry.getKey())).registerAll(entry.getValue());
504  }
505 
506  }
507 
508  public void registerEvent(Class<? extends Event> event, Listener listener, EventPriority priority, EventExecutor executor, Plugin plugin) {
509  registerEvent(event, listener, priority, executor, plugin, false);
510  }
511 
522  public void registerEvent(Class<? extends Event> event, Listener listener, EventPriority priority, EventExecutor executor, Plugin plugin, boolean ignoreCancelled) {
523  Validate.notNull(listener, "Listener cannot be null");
524  Validate.notNull(priority, "Priority cannot be null");
525  Validate.notNull(executor, "Executor cannot be null");
526  Validate.notNull(plugin, "Plugin cannot be null");
527 
528  if (!plugin.isEnabled()) {
529  throw new IllegalPluginAccessException("Plugin attempted to register " + event + " while not enabled");
530  }
531 
532  if (useTimings) {
533  getEventListeners(event).register(new TimedRegisteredListener(listener, executor, priority, plugin, ignoreCancelled));
534  } else {
535  getEventListeners(event).register(new RegisteredListener(listener, executor, priority, plugin, ignoreCancelled));
536  }
537  }
538 
539  private HandlerList getEventListeners(Class<? extends Event> type) {
540  try {
541  Method method = getRegistrationClass(type).getDeclaredMethod("getHandlerList");
542  method.setAccessible(true);
543  return (HandlerList) method.invoke(null);
544  } catch (Exception e) {
545  throw new IllegalPluginAccessException(e.toString());
546  }
547  }
548 
549  private Class<? extends Event> getRegistrationClass(Class<? extends Event> clazz) {
550  try {
551  clazz.getDeclaredMethod("getHandlerList");
552  return clazz;
553  } catch (NoSuchMethodException e) {
554  if (clazz.getSuperclass() != null
555  && !clazz.getSuperclass().equals(Event.class)
556  && Event.class.isAssignableFrom(clazz.getSuperclass())) {
557  return getRegistrationClass(clazz.getSuperclass().asSubclass(Event.class));
558  } else {
559  throw new IllegalPluginAccessException("Unable to find handler list for event " + clazz.getName());
560  }
561  }
562  }
563 
564  public Permission getPermission(String name) {
565  return permissions.get(name.toLowerCase());
566  }
567 
568  public void addPermission(Permission perm) {
569  String name = perm.getName().toLowerCase();
570 
571  if (permissions.containsKey(name)) {
572  throw new IllegalArgumentException("The permission " + name + " is already defined!");
573  }
574 
575  permissions.put(name, perm);
576  calculatePermissionDefault(perm);
577  }
578 
579  public Set<Permission> getDefaultPermissions(boolean op) {
580  return ImmutableSet.copyOf(defaultPerms.get(op));
581  }
582 
583  public void removePermission(Permission perm) {
584  removePermission(perm.getName().toLowerCase());
585  }
586 
587  public void removePermission(String name) {
588  permissions.remove(name);
589  }
590 
592  if (permissions.containsValue(perm)) {
593  defaultPerms.get(true).remove(perm);
594  defaultPerms.get(false).remove(perm);
595 
596  calculatePermissionDefault(perm);
597  }
598  }
599 
600  private void calculatePermissionDefault(Permission perm) {
601  if ((perm.getDefault() == PermissionDefault.OP) || (perm.getDefault() == PermissionDefault.TRUE)) {
602  defaultPerms.get(true).add(perm);
603  dirtyPermissibles(true);
604  }
605  if ((perm.getDefault() == PermissionDefault.NOT_OP) || (perm.getDefault() == PermissionDefault.TRUE)) {
606  defaultPerms.get(false).add(perm);
607  dirtyPermissibles(false);
608  }
609  }
610 
611  private void dirtyPermissibles(boolean op) {
612  Set<Permissible> permissibles = getDefaultPermSubscriptions(op);
613 
614  for (Permissible p : permissibles) {
615  p.recalculatePermissions();
616  }
617  }
618 
619  public void subscribeToPermission(String permission, Permissible permissible) {
620  String name = permission.toLowerCase();
621  Map<Permissible, Boolean> map = permSubs.get(name);
622 
623  if (map == null) {
624  map = new WeakHashMap<Permissible, Boolean>();
625  permSubs.put(name, map);
626  }
627 
628  map.put(permissible, true);
629  }
630 
631  public void unsubscribeFromPermission(String permission, Permissible permissible) {
632  String name = permission.toLowerCase();
633  Map<Permissible, Boolean> map = permSubs.get(name);
634 
635  if (map != null) {
636  map.remove(permissible);
637 
638  if (map.isEmpty()) {
639  permSubs.remove(name);
640  }
641  }
642  }
643 
644  public Set<Permissible> getPermissionSubscriptions(String permission) {
645  String name = permission.toLowerCase();
646  Map<Permissible, Boolean> map = permSubs.get(name);
647 
648  if (map == null) {
649  return ImmutableSet.of();
650  } else {
651  return ImmutableSet.copyOf(map.keySet());
652  }
653  }
654 
655  public void subscribeToDefaultPerms(boolean op, Permissible permissible) {
656  Map<Permissible, Boolean> map = defSubs.get(op);
657 
658  if (map == null) {
659  map = new WeakHashMap<Permissible, Boolean>();
660  defSubs.put(op, map);
661  }
662 
663  map.put(permissible, true);
664  }
665 
666  public void unsubscribeFromDefaultPerms(boolean op, Permissible permissible) {
667  Map<Permissible, Boolean> map = defSubs.get(op);
668 
669  if (map != null) {
670  map.remove(permissible);
671 
672  if (map.isEmpty()) {
673  defSubs.remove(op);
674  }
675  }
676  }
677 
678  public Set<Permissible> getDefaultPermSubscriptions(boolean op) {
679  Map<Permissible, Boolean> map = defSubs.get(op);
680 
681  if (map == null) {
682  return ImmutableSet.of();
683  } else {
684  return ImmutableSet.copyOf(map.keySet());
685  }
686  }
687 
688  public Set<Permission> getPermissions() {
689  return new HashSet<Permission>(permissions.values());
690  }
691 
692  public boolean useTimings() {
693  return useTimings;
694  }
695 
701  public void useTimings(boolean use) {
702  useTimings = use;
703  }
704 }