Fork of Poseidon providing Bukkit #1060 to older Beta versions (b1.0-b1.7.3)
at develop 563 lines 22 kB view raw
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}