1 package org.bukkit.plugin;
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;
16 import java.util.WeakHashMap;
17 import java.util.logging.Level;
18 import java.util.regex.Matcher;
19 import java.util.regex.Pattern;
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;
35 import com.google.common.collect.ImmutableSet;
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;
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>>();
55 this.commandMap = commandMap;
57 defaultPerms.put(
true,
new HashSet<Permission>());
58 defaultPerms.put(
false,
new HashSet<Permission>());
67 public void registerInterface(Class<? extends PluginLoader> loader)
throws IllegalArgumentException {
71 Constructor<? extends PluginLoader> constructor;
74 constructor = loader.getConstructor(
Server.class);
75 instance = constructor.newInstance(server);
76 }
catch (NoSuchMethodException ex) {
77 String className = loader.getName();
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);
84 throw new IllegalArgumentException(String.format(
"Class %s does not implement interface PluginLoader", loader.getName()));
90 for (Pattern pattern : patterns) {
91 fileAssociations.put(pattern, instance);
103 Validate.notNull(directory,
"Directory cannot be null");
104 Validate.isTrue(directory.isDirectory(),
"Directory must be a directory");
106 List<Plugin> result =
new ArrayList<Plugin>();
107 Set<Pattern> filters = fileAssociations.keySet();
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>>();
119 for (File file : directory.listFiles()) {
121 for (Pattern filter : filters) {
122 Matcher match = filter.matcher(file.getName());
124 loader = fileAssociations.get(filter);
128 if (loader == null)
continue;
134 server.
getLogger().log(Level.SEVERE,
"Could not load '" + file.getPath() +
"' in folder '" + directory.getPath() +
"'", ex);
138 plugins.put(description.
getName(), file);
140 Collection<String> softDependencySet = description.
getSoftDepend();
141 if (softDependencySet != null) {
142 if (softDependencies.containsKey(description.
getName())) {
144 softDependencies.get(description.
getName()).addAll(softDependencySet);
146 softDependencies.put(description.
getName(),
new LinkedList<String>(softDependencySet));
150 Collection<String> dependencySet = description.
getDepend();
151 if (dependencySet != null) {
152 dependencies.put(description.
getName(),
new LinkedList<String>(dependencySet));
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());
162 Collection<String> shortSoftDependency =
new LinkedList<String>();
163 shortSoftDependency.add(description.
getName());
164 softDependencies.put(loadBeforeTarget, shortSoftDependency);
170 while (!plugins.isEmpty()) {
171 boolean missingDependency =
true;
172 Iterator<String> pluginIterator = plugins.keySet().iterator();
174 while (pluginIterator.hasNext()) {
175 String plugin = pluginIterator.next();
177 if (dependencies.containsKey(plugin)) {
178 Iterator<String> dependencyIterator = dependencies.get(plugin).iterator();
180 while (dependencyIterator.hasNext()) {
181 String dependency = dependencyIterator.next();
184 if (loadedPlugins.contains(dependency)) {
185 dependencyIterator.remove();
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);
197 "Could not load '" + file.getPath() +
"' in folder '" + directory.getPath() +
"'",
203 if (dependencies.containsKey(plugin) && dependencies.get(plugin).isEmpty()) {
204 dependencies.remove(plugin);
207 if (softDependencies.containsKey(plugin)) {
208 Iterator<String> softDependencyIterator = softDependencies.get(plugin).iterator();
210 while (softDependencyIterator.hasNext()) {
211 String softDependency = softDependencyIterator.next();
214 if (!plugins.containsKey(softDependency)) {
215 softDependencyIterator.remove();
219 if (softDependencies.get(plugin).isEmpty()) {
220 softDependencies.remove(plugin);
223 if (!(dependencies.containsKey(plugin) || softDependencies.containsKey(plugin)) && plugins.containsKey(plugin)) {
225 File file = plugins.get(plugin);
226 pluginIterator.remove();
227 missingDependency =
false;
231 loadedPlugins.add(plugin);
234 server.
getLogger().log(Level.SEVERE,
"Could not load '" + file.getPath() +
"' in folder '" + directory.getPath() +
"'", ex);
239 if (missingDependency) {
242 pluginIterator = plugins.keySet().iterator();
244 while (pluginIterator.hasNext()) {
245 String plugin = pluginIterator.next();
247 if (!dependencies.containsKey(plugin)) {
248 softDependencies.remove(plugin);
249 missingDependency =
false;
250 File file = plugins.get(plugin);
251 pluginIterator.remove();
255 loadedPlugins.add(plugin);
258 server.
getLogger().log(Level.SEVERE,
"Could not load '" + file.getPath() +
"' in folder '" + directory.getPath() +
"'", ex);
263 if (missingDependency) {
264 softDependencies.clear();
265 dependencies.clear();
266 Iterator<File> failedPluginIterator = plugins.values().iterator();
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");
277 return result.toArray(
new Plugin[result.size()]);
291 Validate.notNull(file,
"File cannot be null");
295 Set<Pattern> filters = fileAssociations.keySet();
298 for (Pattern filter : filters) {
300 Matcher match = filter.matcher(name);
309 if (result != null) {
317 private void checkUpdate(File file) {
318 if (updateDirectory == null || !updateDirectory.isDirectory()) {
322 File updateFile =
new File(updateDirectory, file.getName());
323 if (updateFile.isFile() && FileUtil.copy(updateFile, file)) {
337 return lookupNames.get(name);
341 return plugins.toArray(
new Plugin[0]);
365 if ((plugin != null) && (plugins.contains(plugin))) {
376 if (!pluginCommands.isEmpty()) {
382 }
catch (Throwable ex) {
392 for (
int i = plugins.length - 1; i >= 0; i--) {
401 }
catch (Throwable ex) {
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);
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);
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);
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);
433 synchronized (
this) {
438 fileAssociations.clear();
440 defaultPerms.get(
true).clear();
441 defaultPerms.get(
false).clear();
453 if (Thread.holdsLock(
this)) {
454 throw new IllegalStateException(event.
getEventName() +
" cannot be triggered asynchronously from inside synchronized code.");
457 throw new IllegalStateException(event.
getEventName() +
" cannot be triggered asynchronously from primary server thread.");
461 synchronized (
this) {
467 private void fireEvent(
Event event) {
472 if (!registration.getPlugin().isEnabled()) {
478 }
catch (AuthorNagException ex) {
479 Plugin plugin = registration.getPlugin();
481 if (plugin.isNaggable()) {
482 plugin.setNaggable(
false);
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(),
491 }
catch (Throwable ex) {
492 server.
getLogger().log(Level.SEVERE,
"Could not pass event " + event.
getEventName() +
" to " + registration.getPlugin().getDescription().getFullName(), ex);
503 getEventListeners(getRegistrationClass(entry.getKey())).
registerAll(entry.getValue());
509 registerEvent(event, listener, priority, executor, plugin,
false);
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");
539 private HandlerList getEventListeners(Class<? extends Event> type) {
541 Method method = getRegistrationClass(type).getDeclaredMethod(
"getHandlerList");
542 method.setAccessible(
true);
544 }
catch (Exception e) {
545 throw new IllegalPluginAccessException(e.toString());
549 private Class<? extends Event> getRegistrationClass(Class<? extends Event> clazz) {
551 clazz.getDeclaredMethod(
"getHandlerList");
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));
559 throw new IllegalPluginAccessException(
"Unable to find handler list for event " + clazz.getName());
565 return permissions.get(name.toLowerCase());
569 String name = perm.
getName().toLowerCase();
571 if (permissions.containsKey(name)) {
572 throw new IllegalArgumentException(
"The permission " + name +
" is already defined!");
575 permissions.put(name, perm);
576 calculatePermissionDefault(perm);
580 return ImmutableSet.copyOf(defaultPerms.get(op));
588 permissions.remove(name);
592 if (permissions.containsValue(perm)) {
593 defaultPerms.get(
true).remove(perm);
594 defaultPerms.get(
false).remove(perm);
596 calculatePermissionDefault(perm);
600 private void calculatePermissionDefault(
Permission perm) {
602 defaultPerms.get(
true).add(perm);
603 dirtyPermissibles(
true);
606 defaultPerms.get(
false).add(perm);
607 dirtyPermissibles(
false);
611 private void dirtyPermissibles(
boolean op) {
614 for (Permissible p : permissibles) {
615 p.recalculatePermissions();
620 String name = permission.toLowerCase();
621 Map<Permissible, Boolean> map = permSubs.get(name);
624 map =
new WeakHashMap<Permissible, Boolean>();
625 permSubs.put(name, map);
628 map.put(permissible,
true);
632 String name = permission.toLowerCase();
633 Map<Permissible, Boolean> map = permSubs.get(name);
636 map.remove(permissible);
639 permSubs.remove(name);
645 String name = permission.toLowerCase();
646 Map<Permissible, Boolean> map = permSubs.get(name);
649 return ImmutableSet.of();
651 return ImmutableSet.copyOf(map.keySet());
656 Map<Permissible, Boolean> map = defSubs.get(op);
659 map =
new WeakHashMap<Permissible, Boolean>();
660 defSubs.put(op, map);
663 map.put(permissible,
true);
667 Map<Permissible, Boolean> map = defSubs.get(op);
670 map.remove(permissible);
679 Map<Permissible, Boolean> map = defSubs.get(op);
682 return ImmutableSet.of();
684 return ImmutableSet.copyOf(map.keySet());
689 return new HashSet<Permission>(permissions.values());