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 }