001 package org.bukkit.plugin; 002 003 import java.io.InputStream; 004 import java.io.Reader; 005 import java.io.Writer; 006 import java.util.HashMap; 007 import java.util.HashSet; 008 import java.util.List; 009 import java.util.Map; 010 import java.util.Set; 011 012 import org.bukkit.command.CommandExecutor; 013 import org.bukkit.command.PluginCommand; 014 import org.bukkit.plugin.Plugin; 015 import org.bukkit.plugin.java.JavaPlugin; 016 import org.bukkit.permissions.Permissible; 017 import org.bukkit.permissions.Permission; 018 import org.bukkit.permissions.PermissionDefault; 019 import org.yaml.snakeyaml.Yaml; 020 import org.yaml.snakeyaml.constructor.AbstractConstruct; 021 import org.yaml.snakeyaml.constructor.SafeConstructor; 022 import org.yaml.snakeyaml.nodes.Node; 023 import org.yaml.snakeyaml.nodes.Tag; 024 025 import com.google.common.collect.ImmutableList; 026 import com.google.common.collect.ImmutableMap; 027 import com.google.common.collect.ImmutableSet; 028 029 /** 030 * This type is the runtime-container for the information in the plugin.yml. 031 * All plugins must have a respective plugin.yml. For plugins written in java 032 * using the standard plugin loader, this file must be in the root of the jar 033 * file. 034 * <p> 035 * When Bukkit loads a plugin, it needs to know some basic information about 036 * it. It reads this information from a YAML file, 'plugin.yml'. This file 037 * consists of a set of attributes, each defined on a new line and with no 038 * indentation. 039 * <p> 040 * Every (almost* every) method corresponds with a specific entry in the 041 * plugin.yml. These are the <b>required</b> entries for every plugin.yml: 042 * <ul> 043 * <li>{@link #getName()} - <code>name</code> 044 * <li>{@link #getVersion()} - <code>version</code> 045 * <li>{@link #getMain()} - <code>main</code> 046 * </ul> 047 * <p> 048 * Failing to include any of these items will throw an exception and cause the 049 * server to ignore your plugin. 050 * <p> 051 * This is a list of the possible yaml keys, with specific details included in 052 * the respective method documentations: 053 * <table border=1> 054 * <tr> 055 * <th>Node</th> 056 * <th>Method</th> 057 * <th>Summary</th> 058 * </tr><tr> 059 * <td><code>name</code></td> 060 * <td>{@link #getName()}</td> 061 * <td>The unique name of plugin</td> 062 * </tr><tr> 063 * <td><code>version</code></td> 064 * <td>{@link #getVersion()}</td> 065 * <td>A plugin revision identifier</td> 066 * </tr><tr> 067 * <td><code>main</code></td> 068 * <td>{@link #getMain()}</td> 069 * <td>The plugin's initial class file</td> 070 * </tr><tr> 071 * <td><code>author</code><br><code>authors</code></td> 072 * <td>{@link #getAuthors()}</td> 073 * <td>The plugin contributors</td> 074 * </tr><tr> 075 * <td><code>description</code></td> 076 * <td>{@link #getDescription()}</td> 077 * <td>Human readable plugin summary</td> 078 * </tr><tr> 079 * <td><code>website</code></td> 080 * <td>{@link #getWebsite()}</td> 081 * <td>The URL to the plugin's site</td> 082 * </tr><tr> 083 * <td><code>prefix</code></td> 084 * <td>{@link #getPrefix()}</td> 085 * <td>The token to prefix plugin log entries</td> 086 * </tr><tr> 087 * <td><code>database</code></td> 088 * <td>{@link #isDatabaseEnabled()}</td> 089 * <td>Indicator to enable database support</td> 090 * </tr><tr> 091 * <td><code>load</code></td> 092 * <td>{@link #getLoad()}</td> 093 * <td>The phase of server-startup this plugin will load during</td> 094 * </tr><tr> 095 * <td><code>depend</code></td> 096 * <td>{@link #getDepend()}</td> 097 * <td>Other required plugins</td> 098 * </tr><tr> 099 * <td><code>softdepend</code></td> 100 * <td>{@link #getSoftDepend()}</td> 101 * <td>Other plugins that add functionality</td> 102 * </tr><tr> 103 * <td><code>loadbefore</code></td> 104 * <td>{@link #getLoadBefore()}</td> 105 * <td>The inverse softdepend</td> 106 * </tr><tr> 107 * <td><code>commands</code></td> 108 * <td>{@link #getCommands()}</td> 109 * <td>The commands the plugin will register</td> 110 * </tr><tr> 111 * <td><code>permissions</code></td> 112 * <td>{@link #getPermissions()}</td> 113 * <td>The permissions the plugin will register</td> 114 * </tr><tr> 115 * <td><code>default-permission</code></td> 116 * <td>{@link #getPermissionDefault()}</td> 117 * <td>The default {@link Permission#getDefault() default} permission 118 * state for defined {@link #getPermissions() permissions} the plugin 119 * will register</td> 120 * </tr><tr> 121 * <td><code>awareness</code></td> 122 * <td>{@link #getAwareness()}</td> 123 * <td>The concepts that the plugin acknowledges</td> 124 * </tr> 125 * </table> 126 * <p> 127 * A plugin.yml example:<blockquote><pre> 128 *name: Inferno 129 *version: 1.4.1 130 *description: This plugin is so 31337. You can set yourself on fire. 131 *# We could place every author in the authors list, but chose not to for illustrative purposes 132 *# Also, having an author distinguishes that person as the project lead, and ensures their 133 *# name is displayed first 134 *author: CaptainInflamo 135 *authors: [Cogito, verrier, EvilSeph] 136 *website: http://www.curse.com/server-mods/minecraft/myplugin 137 * 138 *main: com.captaininflamo.bukkit.inferno.Inferno 139 *database: false 140 *depend: [NewFire, FlameWire] 141 * 142 *commands: 143 * flagrate: 144 * description: Set yourself on fire. 145 * aliases: [combust_me, combustMe] 146 * permission: inferno.flagrate 147 * usage: Syntax error! Simply type /<command> to ignite yourself. 148 * burningdeaths: 149 * description: List how many times you have died by fire. 150 * aliases: [burning_deaths, burningDeaths] 151 * permission: inferno.burningdeaths 152 * usage: | 153 * /<command> [player] 154 * Example: /<command> - see how many times you have burned to death 155 * Example: /<command> CaptainIce - see how many times CaptainIce has burned to death 156 * 157 *permissions: 158 * inferno.*: 159 * description: Gives access to all Inferno commands 160 * children: 161 * inferno.flagrate: true 162 * inferno.burningdeaths: true 163 * inferno.burningdeaths.others: true 164 * inferno.flagrate: 165 * description: Allows you to ignite yourself 166 * default: true 167 * inferno.burningdeaths: 168 * description: Allows you to see how many times you have burned to death 169 * default: true 170 * inferno.burningdeaths.others: 171 * description: Allows you to see how many times others have burned to death 172 * default: op 173 * children: 174 * inferno.burningdeaths: true 175 *</pre></blockquote> 176 */ 177 public final class PluginDescriptionFile { 178 private static final ThreadLocal<Yaml> YAML = new ThreadLocal<Yaml>() { 179 @Override 180 protected Yaml initialValue() { 181 return new Yaml(new SafeConstructor() { 182 { 183 yamlConstructors.put(null, new AbstractConstruct() { 184 @Override 185 public Object construct(final Node node) { 186 if (!node.getTag().startsWith("!@")) { 187 // Unknown tag - will fail 188 return SafeConstructor.undefinedConstructor.construct(node); 189 } 190 // Unknown awareness - provide a graceful substitution 191 return new PluginAwareness() { 192 @Override 193 public String toString() { 194 return node.toString(); 195 } 196 }; 197 } 198 }); 199 for (final PluginAwareness.Flags flag : PluginAwareness.Flags.values()) { 200 yamlConstructors.put(new Tag("!@" + flag.name()), new AbstractConstruct() { 201 @Override 202 public PluginAwareness.Flags construct(final Node node) { 203 return flag; 204 } 205 }); 206 } 207 } 208 }); 209 } 210 }; 211 String rawName = null; 212 private String name = null; 213 private String main = null; 214 private String classLoaderOf = null; 215 private List<String> depend = ImmutableList.of(); 216 private List<String> softDepend = ImmutableList.of(); 217 private List<String> loadBefore = ImmutableList.of(); 218 private String version = null; 219 private Map<String, Map<String, Object>> commands = null; 220 private String description = null; 221 private List<String> authors = null; 222 private String website = null; 223 private String prefix = null; 224 private boolean database = false; 225 private PluginLoadOrder order = PluginLoadOrder.POSTWORLD; 226 private List<Permission> permissions = null; 227 private Map<?, ?> lazyPermissions = null; 228 private PermissionDefault defaultPerm = PermissionDefault.OP; 229 private Set<PluginAwareness> awareness = ImmutableSet.of(); 230 231 public PluginDescriptionFile(final InputStream stream) throws InvalidDescriptionException { 232 loadMap(asMap(YAML.get().load(stream))); 233 } 234 235 /** 236 * Loads a PluginDescriptionFile from the specified reader 237 * 238 * @param reader The reader 239 * @throws InvalidDescriptionException If the PluginDescriptionFile is 240 * invalid 241 */ 242 public PluginDescriptionFile(final Reader reader) throws InvalidDescriptionException { 243 loadMap(asMap(YAML.get().load(reader))); 244 } 245 246 /** 247 * Creates a new PluginDescriptionFile with the given detailed 248 * 249 * @param pluginName Name of this plugin 250 * @param pluginVersion Version of this plugin 251 * @param mainClass Full location of the main class of this plugin 252 */ 253 public PluginDescriptionFile(final String pluginName, final String pluginVersion, final String mainClass) { 254 name = pluginName.replace(' ', '_'); 255 version = pluginVersion; 256 main = mainClass; 257 } 258 259 /** 260 * Gives the name of the plugin. This name is a unique identifier for 261 * plugins. 262 * <ul> 263 * <li>Must consist of all alphanumeric characters, underscores, hyphon, 264 * and period (a-z,A-Z,0-9, _.-). Any other character will cause the 265 * plugin.yml to fail loading. 266 * <li>Used to determine the name of the plugin's data folder. Data 267 * folders are placed in the ./plugins/ directory by default, but this 268 * behavior should not be relied on. {@link Plugin#getDataFolder()} 269 * should be used to reference the data folder. 270 * <li>It is good practice to name your jar the same as this, for example 271 * 'MyPlugin.jar'. 272 * <li>Case sensitive. 273 * <li>The is the token referenced in {@link #getDepend()}, {@link 274 * #getSoftDepend()}, and {@link #getLoadBefore()}. 275 * <li>Using spaces in the plugin's name is deprecated. 276 * </ul> 277 * <p> 278 * In the plugin.yml, this entry is named <code>name</code>. 279 * <p> 280 * Example:<blockquote><pre>name: MyPlugin</pre></blockquote> 281 * 282 * @return the name of the plugin 283 */ 284 public String getName() { 285 return name; 286 } 287 288 /** 289 * Gives the version of the plugin. 290 * <ul> 291 * <li>Version is an arbitrary string, however the most common format is 292 * MajorRelease.MinorRelease.Build (eg: 1.4.1). 293 * <li>Typically you will increment this every time you release a new 294 * feature or bug fix. 295 * <li>Displayed when a user types <code>/version PluginName</code> 296 * </ul> 297 * <p> 298 * In the plugin.yml, this entry is named <code>version</code>. 299 * <p> 300 * Example:<blockquote><pre>version: 1.4.1</pre></blockquote> 301 * 302 * @return the version of the plugin 303 */ 304 public String getVersion() { 305 return version; 306 } 307 308 /** 309 * Gives the fully qualified name of the main class for a plugin. The 310 * format should follow the {@link ClassLoader#loadClass(String)} syntax 311 * to successfully be resolved at runtime. For most plugins, this is the 312 * class that extends {@link JavaPlugin}. 313 * <ul> 314 * <li>This must contain the full namespace including the class file 315 * itself. 316 * <li>If your namespace is <code>org.bukkit.plugin</code>, and your class 317 * file is called <code>MyPlugin</code> then this must be 318 * <code>org.bukkit.plugin.MyPlugin</code> 319 * <li>No plugin can use <code>org.bukkit.</code> as a base package for 320 * <b>any class</b>, including the main class. 321 * </ul> 322 * <p> 323 * In the plugin.yml, this entry is named <code>main</code>. 324 * <p> 325 * Example: 326 * <blockquote><pre>main: org.bukkit.plugin.MyPlugin</pre></blockquote> 327 * 328 * @return the fully qualified main class for the plugin 329 */ 330 public String getMain() { 331 return main; 332 } 333 334 /** 335 * Gives a human-friendly description of the functionality the plugin 336 * provides. 337 * <ul> 338 * <li>The description can have multiple lines. 339 * <li>Displayed when a user types <code>/version PluginName</code> 340 * </ul> 341 * <p> 342 * In the plugin.yml, this entry is named <code>description</code>. 343 * <p> 344 * Example: 345 * <blockquote><pre>description: This plugin is so 31337. You can set yourself on fire.</pre></blockquote> 346 * 347 * @return description of this plugin, or null if not specified 348 */ 349 public String getDescription() { 350 return description; 351 } 352 353 /** 354 * Gives the phase of server startup that the plugin should be loaded. 355 * <ul> 356 * <li>Possible values are in {@link PluginLoadOrder}. 357 * <li>Defaults to {@link PluginLoadOrder#POSTWORLD}. 358 * <li>Certain caveats apply to each phase. 359 * <li>When different, {@link #getDepend()}, {@link #getSoftDepend()}, and 360 * {@link #getLoadBefore()} become relative in order loaded per-phase. 361 * If a plugin loads at <code>STARTUP</code>, but a dependency loads 362 * at <code>POSTWORLD</code>, the dependency will not be loaded before 363 * the plugin is loaded. 364 * </ul> 365 * <p> 366 * In the plugin.yml, this entry is named <code>load</code>. 367 * <p> 368 * Example:<blockquote><pre>load: STARTUP</pre></blockquote> 369 * 370 * @return the phase when the plugin should be loaded 371 */ 372 public PluginLoadOrder getLoad() { 373 return order; 374 } 375 376 /** 377 * Gives the list of authors for the plugin. 378 * <ul> 379 * <li>Gives credit to the developer. 380 * <li>Used in some server error messages to provide helpful feedback on 381 * who to contact when an error occurs. 382 * <li>A bukkit.org forum handle or email address is recommended. 383 * <li>Is displayed when a user types <code>/version PluginName</code> 384 * <li><code>authors</code> must be in <a 385 * href="http://en.wikipedia.org/wiki/YAML#Lists">YAML list 386 * format</a>. 387 * </ul> 388 * <p> 389 * In the plugin.yml, this has two entries, <code>author</code> and 390 * <code>authors</code>. 391 * <p> 392 * Single author example: 393 * <blockquote><pre>author: CaptainInflamo</pre></blockquote> 394 * Multiple author example: 395 * <blockquote><pre>authors: [Cogito, verrier, EvilSeph]</pre></blockquote> 396 * When both are specified, author will be the first entry in the list, so 397 * this example: 398 * <blockquote><pre>author: Grum 399 *authors: 400 *- feildmaster 401 *- amaranth</pre></blockquote> 402 * Is equivilant to this example: 403 * <blockquote><pre>authors: [Grum, feildmaster, aramanth]<pre></blockquote> 404 * 405 * @return an immutable list of the plugin's authors 406 */ 407 public List<String> getAuthors() { 408 return authors; 409 } 410 411 /** 412 * Gives the plugin's or plugin's author's website. 413 * <ul> 414 * <li>A link to the Curse page that includes documentation and downloads 415 * is highly recommended. 416 * <li>Displayed when a user types <code>/version PluginName</code> 417 * </ul> 418 * <p> 419 * In the plugin.yml, this entry is named <code>website</code>. 420 * <p> 421 * Example: 422 * <blockquote><pre>website: http://www.curse.com/server-mods/minecraft/myplugin</pre></blockquote> 423 * 424 * @return description of this plugin, or null if not specified 425 */ 426 public String getWebsite() { 427 return website; 428 } 429 430 /** 431 * Gives if the plugin uses a database. 432 * <ul> 433 * <li>Using a database is non-trivial. 434 * <li>Valid values include <code>true</code> and <code>false</code> 435 * </ul> 436 * <p> 437 * In the plugin.yml, this entry is named <code>database</code>. 438 * <p> 439 * Example: 440 * <blockquote><pre>database: false</pre></blockquote> 441 * 442 * @return if this plugin requires a database 443 * @see Plugin#getDatabase() 444 */ 445 public boolean isDatabaseEnabled() { 446 return database; 447 } 448 449 /** 450 * Gives a list of other plugins that the plugin requires. 451 * <ul> 452 * <li>Use the value in the {@link #getName()} of the target plugin to 453 * specify the dependency. 454 * <li>If any plugin listed here is not found, your plugin will fail to 455 * load at startup. 456 * <li>If multiple plugins list each other in <code>depend</code>, 457 * creating a network with no individual plugin does not list another 458 * plugin in the <a 459 * href=https://en.wikipedia.org/wiki/Circular_dependency>network</a>, 460 * all plugins in that network will fail. 461 * <li><code>depend</code> must be in must be in <a 462 * href="http://en.wikipedia.org/wiki/YAML#Lists">YAML list 463 * format</a>. 464 * </ul> 465 * <p> 466 * In the plugin.yml, this entry is named <code>depend</code>. 467 * <p> 468 * Example: 469 * <blockquote><pre>depend: 470 *- OnePlugin 471 *- AnotherPlugin</pre></blockquote> 472 * 473 * @return immutable list of the plugin's dependencies 474 */ 475 public List<String> getDepend() { 476 return depend; 477 } 478 479 /** 480 * Gives a list of other plugins that the plugin requires for full 481 * functionality. The {@link PluginManager} will make best effort to treat 482 * all entries here as if they were a {@link #getDepend() dependency}, but 483 * will never fail because of one of these entries. 484 * <ul> 485 * <li>Use the value in the {@link #getName()} of the target plugin to 486 * specify the dependency. 487 * <li>When an unresolvable plugin is listed, it will be ignored and does 488 * not affect load order. 489 * <li>When a circular dependency occurs (a network of plugins depending 490 * or soft-dependending each other), it will arbitrarily choose a 491 * plugin that can be resolved when ignoring soft-dependencies. 492 * <li><code>softdepend</code> must be in <a 493 * href="http://en.wikipedia.org/wiki/YAML#Lists">YAML list 494 * format</a>. 495 * </ul> 496 * <p> 497 * In the plugin.yml, this entry is named <code>softdepend</code>. 498 * <p> 499 * Example: 500 * <blockquote><pre>softdepend: [OnePlugin, AnotherPlugin]</pre></blockquote> 501 * 502 * @return immutable list of the plugin's preferred dependencies 503 */ 504 public List<String> getSoftDepend() { 505 return softDepend; 506 } 507 508 /** 509 * Gets the list of plugins that should consider this plugin a 510 * soft-dependency. 511 * <ul> 512 * <li>Use the value in the {@link #getName()} of the target plugin to 513 * specify the dependency. 514 * <li>The plugin should load before any other plugins listed here. 515 * <li>Specifying another plugin here is strictly equivalent to having the 516 * specified plugin's {@link #getSoftDepend()} include {@link 517 * #getName() this plugin}. 518 * <li><code>loadbefore</code> must be in <a 519 * href="http://en.wikipedia.org/wiki/YAML#Lists">YAML list 520 * format</a>. 521 * </ul> 522 * <p> 523 * In the plugin.yml, this entry is named <code>loadbefore</code>. 524 * <p> 525 * Example: 526 * <blockquote><pre>loadbefore: 527 *- OnePlugin 528 *- AnotherPlugin</pre></blockquote> 529 * 530 * @return immutable list of plugins that should consider this plugin a 531 * soft-dependency 532 */ 533 public List<String> getLoadBefore() { 534 return loadBefore; 535 } 536 537 /** 538 * Gives the token to prefix plugin-specific logging messages with. 539 * <ul> 540 * <li>This includes all messages using {@link Plugin#getLogger()}. 541 * <li>If not specified, the server uses the plugin's {@link #getName() 542 * name}. 543 * <li>This should clearly indicate what plugin is being logged. 544 * </ul> 545 * <p> 546 * In the plugin.yml, this entry is named <code>prefix</code>. 547 * <p> 548 * Example:<blockquote><pre>prefix: ex-why-zee</pre></blockquote> 549 * 550 * @return the prefixed logging token, or null if not specified 551 */ 552 public String getPrefix() { 553 return prefix; 554 } 555 556 /** 557 * Gives the map of command-name to command-properties. Each entry in this 558 * map corresponds to a single command and the respective values are the 559 * properties of the command. Each property, <i>with the exception of 560 * aliases</i>, can be defined at runtime using methods in {@link 561 * PluginCommand} and are defined here only as a convenience. 562 * <table border=1> 563 * <tr> 564 * <th>Node</th> 565 * <th>Method</th> 566 * <th>Type</th> 567 * <th>Description</th> 568 * <th>Example</th> 569 * </tr><tr> 570 * <td><code>description</code></td> 571 * <td>{@link PluginCommand#setDescription(String)}</td> 572 * <td>String</td> 573 * <td>A user-friendly description for a command. It is useful for 574 * documentation purposes as well as in-game help.</td> 575 * <td><blockquote><pre>description: Set yourself on fire</pre></blockquote></td> 576 * </tr><tr> 577 * <td><code>aliases</code></td> 578 * <td>{@link PluginCommand#setAliases(List)}</td> 579 * <td>String or <a 580 * href="http://en.wikipedia.org/wiki/YAML#Lists">List</a> of 581 * strings</td> 582 * <td>Alternative command names, with special usefulness for commands 583 * that are already registered. <i>Aliases are not effective when 584 * defined at runtime,</i> so the plugin description file is the 585 * only way to have them properly defined. 586 * <p> 587 * Note: Command aliases may not have a colon in them.</td> 588 * <td>Single alias format: 589 * <blockquote><pre>aliases: combust_me</pre></blockquote> or 590 * multiple alias format: 591 * <blockquote><pre>aliases: [combust_me, combustMe]</pre></blockquote></td> 592 * </tr><tr> 593 * <td><code>permission</code></td> 594 * <td>{@link PluginCommand#setPermission(String)}</td> 595 * <td>String</td> 596 * <td>The name of the {@link Permission} required to use the command. 597 * A user without the permission will receive the specified 598 * message (see {@linkplain 599 * PluginCommand#setPermissionMessage(String) below}), or a 600 * standard one if no specific message is defined. Without the 601 * permission node, no {@link 602 * PluginCommand#setExecutor(CommandExecutor) CommandExecutor} or 603 * {@link PluginCommand#setTabCompleter(TabCompleter) 604 * TabCompleter} will be called.</td> 605 * <td><blockquote><pre>permission: inferno.flagrate</pre></blockquote></td> 606 * </tr><tr> 607 * <td><code>permission-message</code></td> 608 * <td>{@link PluginCommand#setPermissionMessage(String)}</td> 609 * <td>String</td> 610 * <td><ul> 611 * <li>Displayed to a player that attempts to use a command, but 612 * does not have the required permission. See {@link 613 * PluginCommand#getPermission() above}. 614 * <li><permission> is a macro that is replaced with the 615 * permission node required to use the command. 616 * <li>Using empty quotes is a valid way to indicate nothing 617 * should be displayed to a player. 618 * </ul></td> 619 * <td><blockquote><pre>permission-message: You do not have /<permission></pre></blockquote></td> 620 * </tr><tr> 621 * <td><code>usage</code></td> 622 * <td>{@link PluginCommand#setUsage(String)}</td> 623 * <td>String</td> 624 * <td>This message is displayed to a player when the {@link 625 * PluginCommand#setExecutor(CommandExecutor)} {@linkplain 626 * CommandExecutor#onCommand(CommandSender,Command,String,String[]) 627 * returns false}. <command> is a macro that is replaced 628 * the command issued.</td> 629 * <td><blockquote><pre>usage: Syntax error! Perhaps you meant /<command> PlayerName?</pre></blockquote> 630 * It is worth noting that to use a colon in a yaml, like 631 * <code>`usage: Usage: /god [player]'</code>, you need to 632 * <a href="http://yaml.org/spec/current.html#id2503232">surround 633 * the message with double-quote</a>: 634 * <blockquote><pre>usage: "Usage: /god [player]"</pre></blockquote></td> 635 * </tr> 636 * </table> 637 * The commands are structured as a hiearchy of <a 638 * href="http://yaml.org/spec/current.html#id2502325">nested mappings</a>. 639 * The primary (top-level, no intendentation) node is 640 * `<code>commands</code>', while each individual command name is 641 * indented, indicating it maps to some value (in our case, the 642 * properties of the table above). 643 * <p> 644 * Here is an example bringing together the piecemeal examples above, as 645 * well as few more definitions:<blockquote><pre> 646 *commands: 647 * flagrate: 648 * description: Set yourself on fire. 649 * aliases: [combust_me, combustMe] 650 * permission: inferno.flagrate 651 * permission-message: You do not have /<permission> 652 * usage: Syntax error! Perhaps you meant /<command> PlayerName? 653 * burningdeaths: 654 * description: List how many times you have died by fire. 655 * aliases: 656 * - burning_deaths 657 * - burningDeaths 658 * permission: inferno.burningdeaths 659 * usage: | 660 * /<command> [player] 661 * Example: /<command> - see how many times you have burned to death 662 * Example: /<command> CaptainIce - see how many times CaptainIce has burned to death 663 * # The next command has no description, aliases, etc. defined, but is still valid 664 * # Having an empty declaration is useful for defining the description, permission, and messages from a configuration dynamically 665 * apocalypse: 666 *</pre></blockquote> 667 * Note: Command names may not have a colon in their name. 668 * 669 * @return the commands this plugin will register 670 */ 671 public Map<String, Map<String, Object>> getCommands() { 672 return commands; 673 } 674 675 /** 676 * Gives the list of permissions the plugin will register at runtime, 677 * immediately proceding enabling. The format for defining permissions is 678 * a map from permission name to properties. To represent a map without 679 * any specific property, empty <a 680 * href="http://yaml.org/spec/current.html#id2502702">curly-braces</a> ( 681 * <code>{}</code> ) may be used (as a null value is not 682 * accepted, unlike the {@link #getCommands() commands} above). 683 * <p> 684 * A list of optional properties for permissions: 685 * <table border=1> 686 * <tr> 687 * <th>Node</th> 688 * <th>Description</th> 689 * <th>Example</th> 690 * </tr><tr> 691 * <td><code>description</code></td> 692 * <td>Plaintext (user-friendly) description of what the permission 693 * is for.</td> 694 * <td><blockquote><pre>description: Allows you to set yourself on fire</pre></blockquote></td> 695 * </tr><tr> 696 * <td><code>default</code></td> 697 * <td>The default state for the permission, as defined by {@link 698 * Permission#getDefault()}. If not defined, it will be set to 699 * the value of {@link PluginDescriptionFile#getPermissionDefault()}. 700 * <p> 701 * For reference:<ul> 702 * <li><code>true</code> - Represents a positive assignment to 703 * {@link Permissible permissibles}. 704 * <li><code>false</code> - Represents no assignment to {@link 705 * Permissible permissibles}. 706 * <li><code>op</code> - Represents a positive assignment to 707 * {@link Permissible#isOp() operator permissibles}. 708 * <li><code>notop</code> - Represents a positive assignment to 709 * {@link Permissible#isOp() non-operator permissibiles}. 710 * </ul></td> 711 * <td><blockquote><pre>default: true</pre></blockquote></td> 712 * </tr><tr> 713 * <td><code>children</code></td> 714 * <td>Allows other permissions to be set as a {@linkplain 715 * Permission#getChildren() relation} to the parent permission. 716 * When a parent permissions is assigned, child permissions are 717 * respectively assigned as well. 718 * <ul> 719 * <li>When a parent permission is assigned negatively, child 720 * permissions are assigned based on an inversion of their 721 * association. 722 * <li>When a parent permission is assigned positively, child 723 * permissions are assigned based on their association. 724 * </ul> 725 * <p> 726 * Child permissions may be defined in a number of ways:<ul> 727 * <li>Children may be defined as a <a 728 * href="http://en.wikipedia.org/wiki/YAML#Lists">list</a> of 729 * names. Using a list will treat all children associated 730 * positively to their parent. 731 * <li>Children may be defined as a map. Each permission name maps 732 * to either a boolean (representing the association), or a 733 * nested permission definition (just as another permission). 734 * Using a nested definition treats the child as a positive 735 * association. 736 * <li>A nested permission definition must be a map of these same 737 * properties. To define a valid nested permission without 738 * defining any specific property, empty curly-braces ( 739 * <code>{}</code> ) must be used. 740 * <li>A nested permission may carry it's own nested permissions 741 * as children, as they may also have nested permissions, and 742 * so forth. There is no direct limit to how deep the 743 * permission tree is defined. 744 * </ul></td> 745 * <td>As a list: 746 * <blockquote><pre>children: [inferno.flagrate, inferno.burningdeaths]</pre></blockquote> 747 * Or as a mapping: 748 * <blockquote><pre>children: 749 * inferno.flagrate: true 750 * inferno.burningdeaths: true</pre></blockquote> 751 * An additional example showing basic nested values can be seen 752 * <a href="doc-files/permissions-example_plugin.yml">here</a>. 753 * </td> 754 * </tr> 755 * </table> 756 * The permissions are structured as a hiearchy of <a 757 * href="http://yaml.org/spec/current.html#id2502325">nested mappings</a>. 758 * The primary (top-level, no intendentation) node is 759 * `<code>permissions</code>', while each individual permission name is 760 * indented, indicating it maps to some value (in our case, the 761 * properties of the table above). 762 * <p> 763 * Here is an example using some of the properties:<blockquote><pre> 764 *permissions: 765 * inferno.*: 766 * description: Gives access to all Inferno commands 767 * children: 768 * inferno.flagrate: true 769 * inferno.burningdeaths: true 770 * inferno.flagate: 771 * description: Allows you to ignite yourself 772 * default: true 773 * inferno.burningdeaths: 774 * description: Allows you to see how many times you have burned to death 775 * default: true 776 *</pre></blockquote> 777 * Another example, with nested definitions, can be found <a 778 * href="doc-files/permissions-example_plugin.yml">here</a>. 779 */ 780 public List<Permission> getPermissions() { 781 if (permissions == null) { 782 if (lazyPermissions == null) { 783 permissions = ImmutableList.<Permission>of(); 784 } else { 785 permissions = ImmutableList.copyOf(Permission.loadPermissions(lazyPermissions, "Permission node '%s' in plugin description file for " + getFullName() + " is invalid", defaultPerm)); 786 lazyPermissions = null; 787 } 788 } 789 return permissions; 790 } 791 792 /** 793 * Gives the default {@link Permission#getDefault() default} state of 794 * {@link #getPermissions() permissions} registered for the plugin. 795 * <ul> 796 * <li>If not specified, it will be {@link PermissionDefault#OP}. 797 * <li>It is matched using {@link PermissionDefault#getByName(String)} 798 * <li>It only affects permissions that do not define the 799 * <code>default</code> node. 800 * <li>It may be any value in {@link PermissionDefault}. 801 * </ul> 802 * <p> 803 * In the plugin.yml, this entry is named <code>default-permission</code>. 804 * <p> 805 * Example:<blockquote><pre>default-permission: NOT_OP</pre></blockquote> 806 * 807 * @return the default value for the plugin's permissions 808 */ 809 public PermissionDefault getPermissionDefault() { 810 return defaultPerm; 811 } 812 813 /** 814 * Gives a set of every {@link PluginAwareness} for a plugin. An awareness 815 * dictates something that a plugin developer acknowledges when the plugin 816 * is compiled. Some implementions may define extra awarenesses that are 817 * not included in the API. Any unrecognized 818 * awareness (one unsupported or in a future version) will cause a dummy 819 * object to be created instead of failing. 820 * <p> 821 * <ul> 822 * <li>Currently only supports the enumerated values in {@link 823 * PluginAwareness.Flags}. 824 * <li>Each awareness starts the identifier with bang-at 825 * (<code>!@</code>). 826 * <li>Unrecognized (future / unimplemented) entries are quietly replaced 827 * by a generic object that implements PluginAwareness. 828 * <li>A type of awareness must be defined by the runtime and acknowledged 829 * by the API, effectively discluding any derived type from any 830 * plugin's classpath. 831 * <li><code>awareness</code> must be in <a 832 * href="http://en.wikipedia.org/wiki/YAML#Lists">YAML list 833 * format</a>. 834 * </ul> 835 * <p> 836 * In the plugin.yml, this entry is named <code>awareness</code>. 837 * <p> 838 * Example:<blockquote><pre>awareness: 839 *- !@UTF8</pre></blockquote> 840 * <p> 841 * <b>Note:</b> Although unknown versions of some future awareness are 842 * gracefully substituted, previous versions of Bukkit (ones prior to the 843 * first implementation of awareness) will fail to load a plugin that 844 * defines any awareness. 845 * 846 * @return a set containing every awareness for the plugin 847 */ 848 public Set<PluginAwareness> getAwareness() { 849 return awareness; 850 } 851 852 /** 853 * Returns the name of a plugin, including the version. This method is 854 * provided for convenience; it uses the {@link #getName()} and {@link 855 * #getVersion()} entries. 856 * 857 * @return a descriptive name of the plugin and respective version 858 */ 859 public String getFullName() { 860 return name + " v" + version; 861 } 862 863 /** 864 * @deprecated unused 865 */ 866 @Deprecated 867 public String getClassLoaderOf() { 868 return classLoaderOf; 869 } 870 871 public void setDatabaseEnabled(boolean database) { 872 this.database = database; 873 } 874 875 /** 876 * Saves this PluginDescriptionFile to the given writer 877 * 878 * @param writer Writer to output this file to 879 */ 880 public void save(Writer writer) { 881 YAML.get().dump(saveMap(), writer); 882 } 883 884 private void loadMap(Map<?, ?> map) throws InvalidDescriptionException { 885 try { 886 name = rawName = map.get("name").toString(); 887 888 if (!name.matches("^[A-Za-z0-9 _.-]+$")) { 889 throw new InvalidDescriptionException("name '" + name + "' contains invalid characters."); 890 } 891 name = name.replace(' ', '_'); 892 } catch (NullPointerException ex) { 893 throw new InvalidDescriptionException(ex, "name is not defined"); 894 } catch (ClassCastException ex) { 895 throw new InvalidDescriptionException(ex, "name is of wrong type"); 896 } 897 898 try { 899 version = map.get("version").toString(); 900 } catch (NullPointerException ex) { 901 throw new InvalidDescriptionException(ex, "version is not defined"); 902 } catch (ClassCastException ex) { 903 throw new InvalidDescriptionException(ex, "version is of wrong type"); 904 } 905 906 try { 907 main = map.get("main").toString(); 908 if (main.startsWith("org.bukkit.")) { 909 throw new InvalidDescriptionException("main may not be within the org.bukkit namespace"); 910 } 911 } catch (NullPointerException ex) { 912 throw new InvalidDescriptionException(ex, "main is not defined"); 913 } catch (ClassCastException ex) { 914 throw new InvalidDescriptionException(ex, "main is of wrong type"); 915 } 916 917 if (map.get("commands") != null) { 918 ImmutableMap.Builder<String, Map<String, Object>> commandsBuilder = ImmutableMap.<String, Map<String, Object>>builder(); 919 try { 920 for (Map.Entry<?, ?> command : ((Map<?, ?>) map.get("commands")).entrySet()) { 921 ImmutableMap.Builder<String, Object> commandBuilder = ImmutableMap.<String, Object>builder(); 922 if (command.getValue() != null) { 923 for (Map.Entry<?, ?> commandEntry : ((Map<?, ?>) command.getValue()).entrySet()) { 924 if (commandEntry.getValue() instanceof Iterable) { 925 // This prevents internal alias list changes 926 ImmutableList.Builder<Object> commandSubList = ImmutableList.<Object>builder(); 927 for (Object commandSubListItem : (Iterable<?>) commandEntry.getValue()) { 928 if (commandSubListItem != null) { 929 commandSubList.add(commandSubListItem); 930 } 931 } 932 commandBuilder.put(commandEntry.getKey().toString(), commandSubList.build()); 933 } else if (commandEntry.getValue() != null) { 934 commandBuilder.put(commandEntry.getKey().toString(), commandEntry.getValue()); 935 } 936 } 937 } 938 commandsBuilder.put(command.getKey().toString(), commandBuilder.build()); 939 } 940 } catch (ClassCastException ex) { 941 throw new InvalidDescriptionException(ex, "commands are of wrong type"); 942 } 943 commands = commandsBuilder.build(); 944 } 945 946 if (map.get("class-loader-of") != null) { 947 classLoaderOf = map.get("class-loader-of").toString(); 948 } 949 950 depend = makePluginNameList(map, "depend"); 951 softDepend = makePluginNameList(map, "softdepend"); 952 loadBefore = makePluginNameList(map, "loadbefore"); 953 954 if (map.get("database") != null) { 955 try { 956 database = (Boolean) map.get("database"); 957 } catch (ClassCastException ex) { 958 throw new InvalidDescriptionException(ex, "database is of wrong type"); 959 } 960 } 961 962 if (map.get("website") != null) { 963 website = map.get("website").toString(); 964 } 965 966 if (map.get("description") != null) { 967 description = map.get("description").toString(); 968 } 969 970 if (map.get("load") != null) { 971 try { 972 order = PluginLoadOrder.valueOf(((String) map.get("load")).toUpperCase().replaceAll("\\W", "")); 973 } catch (ClassCastException ex) { 974 throw new InvalidDescriptionException(ex, "load is of wrong type"); 975 } catch (IllegalArgumentException ex) { 976 throw new InvalidDescriptionException(ex, "load is not a valid choice"); 977 } 978 } 979 980 if (map.get("authors") != null) { 981 ImmutableList.Builder<String> authorsBuilder = ImmutableList.<String>builder(); 982 if (map.get("author") != null) { 983 authorsBuilder.add(map.get("author").toString()); 984 } 985 try { 986 for (Object o : (Iterable<?>) map.get("authors")) { 987 authorsBuilder.add(o.toString()); 988 } 989 } catch (ClassCastException ex) { 990 throw new InvalidDescriptionException(ex, "authors are of wrong type"); 991 } catch (NullPointerException ex) { 992 throw new InvalidDescriptionException(ex, "authors are improperly defined"); 993 } 994 authors = authorsBuilder.build(); 995 } else if (map.get("author") != null) { 996 authors = ImmutableList.of(map.get("author").toString()); 997 } else { 998 authors = ImmutableList.<String>of(); 999 } 1000 1001 if (map.get("default-permission") != null) { 1002 try { 1003 defaultPerm = PermissionDefault.getByName(map.get("default-permission").toString()); 1004 } catch (ClassCastException ex) { 1005 throw new InvalidDescriptionException(ex, "default-permission is of wrong type"); 1006 } catch (IllegalArgumentException ex) { 1007 throw new InvalidDescriptionException(ex, "default-permission is not a valid choice"); 1008 } 1009 } 1010 1011 if (map.get("awareness") instanceof Iterable) { 1012 Set<PluginAwareness> awareness = new HashSet<PluginAwareness>(); 1013 try { 1014 for (Object o : (Iterable<?>) map.get("awareness")) { 1015 awareness.add((PluginAwareness) o); 1016 } 1017 } catch (ClassCastException ex) { 1018 throw new InvalidDescriptionException(ex, "awareness has wrong type"); 1019 } 1020 this.awareness = ImmutableSet.copyOf(awareness); 1021 } 1022 1023 try { 1024 lazyPermissions = (Map<?, ?>) map.get("permissions"); 1025 } catch (ClassCastException ex) { 1026 throw new InvalidDescriptionException(ex, "permissions are of the wrong type"); 1027 } 1028 1029 if (map.get("prefix") != null) { 1030 prefix = map.get("prefix").toString(); 1031 } 1032 } 1033 1034 private static List<String> makePluginNameList(final Map<?, ?> map, final String key) throws InvalidDescriptionException { 1035 final Object value = map.get(key); 1036 if (value == null) { 1037 return ImmutableList.of(); 1038 } 1039 1040 final ImmutableList.Builder<String> builder = ImmutableList.<String>builder(); 1041 try { 1042 for (final Object entry : (Iterable<?>) value) { 1043 builder.add(entry.toString().replace(' ', '_')); 1044 } 1045 } catch (ClassCastException ex) { 1046 throw new InvalidDescriptionException(ex, key + " is of wrong type"); 1047 } catch (NullPointerException ex) { 1048 throw new InvalidDescriptionException(ex, "invalid " + key + " format"); 1049 } 1050 return builder.build(); 1051 } 1052 1053 private Map<String, Object> saveMap() { 1054 Map<String, Object> map = new HashMap<String, Object>(); 1055 1056 map.put("name", name); 1057 map.put("main", main); 1058 map.put("version", version); 1059 map.put("database", database); 1060 map.put("order", order.toString()); 1061 map.put("default-permission", defaultPerm.toString()); 1062 1063 if (commands != null) { 1064 map.put("command", commands); 1065 } 1066 if (depend != null) { 1067 map.put("depend", depend); 1068 } 1069 if (softDepend != null) { 1070 map.put("softdepend", softDepend); 1071 } 1072 if (website != null) { 1073 map.put("website", website); 1074 } 1075 if (description != null) { 1076 map.put("description", description); 1077 } 1078 1079 if (authors.size() == 1) { 1080 map.put("author", authors.get(0)); 1081 } else if (authors.size() > 1) { 1082 map.put("authors", authors); 1083 } 1084 1085 if (classLoaderOf != null) { 1086 map.put("class-loader-of", classLoaderOf); 1087 } 1088 1089 if (prefix != null) { 1090 map.put("prefix", prefix); 1091 } 1092 1093 return map; 1094 } 1095 1096 private Map<?,?> asMap(Object object) throws InvalidDescriptionException { 1097 if (object instanceof Map) { 1098 return (Map<?,?>) object; 1099 } 1100 throw new InvalidDescriptionException(object + " is not properly structured."); 1101 } 1102 1103 /** 1104 * @deprecated Internal use 1105 */ 1106 @Deprecated 1107 public String getRawName() { 1108 return rawName; 1109 } 1110 }