
org.bukkit.plugin.PluginDescriptionFile Maven / Gradle / Ivy
Show all versions of walk-server Show documentation
package org.bukkit.plugin;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender;
import org.bukkit.command.PluginCommand;
import org.bukkit.command.TabCompleter;
import org.bukkit.permissions.Permissible;
import org.bukkit.permissions.Permission;
import org.bukkit.permissions.PermissionDefault;
import org.bukkit.plugin.java.JavaPlugin;
import org.yaml.snakeyaml.Yaml;
import org.yaml.snakeyaml.constructor.AbstractConstruct;
import org.yaml.snakeyaml.constructor.SafeConstructor;
import org.yaml.snakeyaml.nodes.Node;
import org.yaml.snakeyaml.nodes.Tag;
import java.io.InputStream;
import java.io.Reader;
import java.io.Writer;
import java.util.*;
/**
* This type is the runtime-container for the information in the plugin.yml.
* All plugins must have a respective plugin.yml. For plugins written in java
* using the standard plugin loader, this file must be in the root of the jar
* file.
*
* When Bukkit loads a plugin, it needs to know some basic information about
* it. It reads this information from a YAML file, 'plugin.yml'. This file
* consists of a set of attributes, each defined on a new line and with no
* indentation.
*
* Every (almost* every) method corresponds with a specific entry in the
* plugin.yml. These are the required entries for every plugin.yml:
*
* - {@link #getName()} -
name
* - {@link #getVersion()} -
version
* - {@link #getMain()} -
main
*
*
* Failing to include any of these items will throw an exception and cause the
* server to ignore your plugin.
*
* This is a list of the possible yaml keys, with specific details included in
* the respective method documentations:
*
* The description of the plugin.yml layout
*
* Node
* Method
* Summary
*
* name
* {@link #getName()}
* The unique name of plugin
*
* version
* {@link #getVersion()}
* A plugin revision identifier
*
* main
* {@link #getMain()}
* The plugin's initial class file
*
* author
authors
* {@link #getAuthors()}
* The plugin contributors
*
* description
* {@link #getDescription()}
* Human readable plugin summary
*
* website
* {@link #getWebsite()}
* The URL to the plugin's site
*
* prefix
* {@link #getPrefix()}
* The token to prefix plugin log entries
*
* database
* {@link #isDatabaseEnabled()}
* Indicator to enable database support
*
* load
* {@link #getLoad()}
* The phase of server-startup this plugin will load during
*
* depend
* {@link #getDepend()}
* Other required plugins
*
* softdepend
* {@link #getSoftDepend()}
* Other plugins that add functionality
*
* loadbefore
* {@link #getLoadBefore()}
* The inverse softdepend
*
* commands
* {@link #getCommands()}
* The commands the plugin will register
*
* permissions
* {@link #getPermissions()}
* The permissions the plugin will register
*
* default-permission
* {@link #getPermissionDefault()}
* The default {@link Permission#getDefault() default} permission
* state for defined {@link #getPermissions() permissions} the plugin
* will register
*
* awareness
* {@link #getAwareness()}
* The concepts that the plugin acknowledges
*
*
*
* A plugin.yml example:
* name: Inferno
* version: 1.4.1
* description: This plugin is so 31337. You can set yourself on fire.
* # We could place every author in the authors list, but chose not to for illustrative purposes
* # Also, having an author distinguishes that person as the project lead, and ensures their
* # name is displayed first
* author: CaptainInflamo
* authors: [Cogito, verrier, EvilSeph]
* website: http://www.curse.com/server-mods/minecraft/myplugin
*
* main: com.captaininflamo.bukkit.inferno.Inferno
* database: false
* depend: [NewFire, FlameWire]
*
* commands:
* flagrate:
* description: Set yourself on fire.
* aliases: [combust_me, combustMe]
* permission: inferno.flagrate
* usage: Syntax error! Simply type /<command> to ignite yourself.
* burningdeaths:
* description: List how many times you have died by fire.
* aliases: [burning_deaths, burningDeaths]
* permission: inferno.burningdeaths
* usage: |
* /<command> [player]
* Example: /<command> - see how many times you have burned to death
* Example: /<command> CaptainIce - see how many times CaptainIce has burned to death
*
* permissions:
* inferno.*:
* description: Gives access to all Inferno commands
* children:
* inferno.flagrate: true
* inferno.burningdeaths: true
* inferno.burningdeaths.others: true
* inferno.flagrate:
* description: Allows you to ignite yourself
* default: true
* inferno.burningdeaths:
* description: Allows you to see how many times you have burned to death
* default: true
* inferno.burningdeaths.others:
* description: Allows you to see how many times others have burned to death
* default: op
* children:
* inferno.burningdeaths: true
*
*/
public final class PluginDescriptionFile {
private static final ThreadLocal YAML = new ThreadLocal() {
@Override
protected Yaml initialValue() {
return new Yaml(new SafeConstructor() {
{
yamlConstructors.put(null, new AbstractConstruct() {
@Override
public Object construct(final Node node) {
if (!node.getTag().startsWith("!@")) {
// Unknown tag - will fail
return SafeConstructor.undefinedConstructor.construct(node);
}
// Unknown awareness - provide a graceful substitution
return new PluginAwareness() {
@Override
public String toString() {
return node.toString();
}
};
}
});
for (final PluginAwareness.Flags flag : PluginAwareness.Flags.values()) {
yamlConstructors.put(new Tag("!@" + flag.name()), new AbstractConstruct() {
@Override
public PluginAwareness.Flags construct(final Node node) {
return flag;
}
});
}
}
});
}
};
String rawName = null;
private String name = null;
private String main = null;
private String classLoaderOf = null;
private List depend = ImmutableList.of();
private List softDepend = ImmutableList.of();
private List loadBefore = ImmutableList.of();
private String version = null;
private Map> commands = null;
private String description = null;
private List authors = null;
private String website = null;
private String prefix = null;
private boolean database = false;
private PluginLoadOrder order = PluginLoadOrder.POSTWORLD;
private List permissions = null;
private Map, ?> lazyPermissions = null;
private PermissionDefault defaultPerm = PermissionDefault.OP;
private Set awareness = ImmutableSet.of();
public PluginDescriptionFile(final InputStream stream) throws InvalidDescriptionException {
loadMap(asMap(YAML.get().load(stream)));
}
/**
* Loads a PluginDescriptionFile from the specified reader
*
* @param reader The reader
* @throws InvalidDescriptionException If the PluginDescriptionFile is
* invalid
*/
public PluginDescriptionFile(final Reader reader) throws InvalidDescriptionException {
loadMap(asMap(YAML.get().load(reader)));
}
/**
* Creates a new PluginDescriptionFile with the given detailed
*
* @param pluginName Name of this plugin
* @param pluginVersion Version of this plugin
* @param mainClass Full location of the main class of this plugin
*/
public PluginDescriptionFile(final String pluginName, final String pluginVersion, final String mainClass) {
name = pluginName.replace(' ', '_');
version = pluginVersion;
main = mainClass;
}
private static List makePluginNameList(final Map, ?> map, final String key) throws InvalidDescriptionException {
final Object value = map.get(key);
if (value == null) {
return ImmutableList.of();
}
final ImmutableList.Builder builder = ImmutableList.builder();
try {
for (final Object entry : (Iterable>) value) {
builder.add(entry.toString().replace(' ', '_'));
}
} catch (ClassCastException ex) {
throw new InvalidDescriptionException(ex, key + " is of wrong type");
} catch (NullPointerException ex) {
throw new InvalidDescriptionException(ex, "invalid " + key + " format");
}
return builder.build();
}
/**
* Gives the name of the plugin. This name is a unique identifier for
* plugins.
*
* - Must consist of all alphanumeric characters, underscores, hyphon,
* and period (a-z,A-Z,0-9, _.-). Any other character will cause the
* plugin.yml to fail loading.
*
- Used to determine the name of the plugin's data folder. Data
* folders are placed in the ./plugins/ directory by default, but this
* behavior should not be relied on. {@link Plugin#getDataFolder()}
* should be used to reference the data folder.
*
- It is good practice to name your jar the same as this, for example
* 'MyPlugin.jar'.
*
- Case sensitive.
*
- The is the token referenced in {@link #getDepend()}, {@link
* #getSoftDepend()}, and {@link #getLoadBefore()}.
*
- Using spaces in the plugin's name is deprecated.
*
*
* In the plugin.yml, this entry is named name
.
*
* Example:
name: MyPlugin
*
* @return the name of the plugin
*/
public String getName() {
return name;
}
/**
* Gives the version of the plugin.
*
* - Version is an arbitrary string, however the most common format is
* MajorRelease.MinorRelease.Build (eg: 1.4.1).
*
- Typically you will increment this every time you release a new
* feature or bug fix.
*
- Displayed when a user types
/version PluginName
*
*
* In the plugin.yml, this entry is named version
.
*
* Example:
version: 1.4.1
*
* @return the version of the plugin
*/
public String getVersion() {
return version;
}
/**
* Gives the fully qualified name of the main class for a plugin. The
* format should follow the {@link ClassLoader#loadClass(String)} syntax
* to successfully be resolved at runtime. For most plugins, this is the
* class that extends {@link JavaPlugin}.
*
* - This must contain the full namespace including the class file
* itself.
*
- If your namespace is
org.bukkit.plugin
, and your class
* file is called MyPlugin
then this must be
* org.bukkit.plugin.MyPlugin
* - No plugin can use
org.bukkit.
as a base package for
* any class, including the main class.
*
*
* In the plugin.yml, this entry is named main
.
*
* Example:
*
main: org.bukkit.plugin.MyPlugin
*
* @return the fully qualified main class for the plugin
*/
public String getMain() {
return main;
}
/**
* Gives a human-friendly description of the functionality the plugin
* provides.
*
* - The description can have multiple lines.
*
- Displayed when a user types
/version PluginName
*
*
* In the plugin.yml, this entry is named description
.
*
* Example:
*
description: This plugin is so 31337. You can set yourself on fire.
*
* @return description of this plugin, or null if not specified
*/
public String getDescription() {
return description;
}
/**
* Gives the phase of server startup that the plugin should be loaded.
*
* - Possible values are in {@link PluginLoadOrder}.
*
- Defaults to {@link PluginLoadOrder#POSTWORLD}.
*
- Certain caveats apply to each phase.
*
- When different, {@link #getDepend()}, {@link #getSoftDepend()}, and
* {@link #getLoadBefore()} become relative in order loaded per-phase.
* If a plugin loads at
STARTUP
, but a dependency loads
* at POSTWORLD
, the dependency will not be loaded before
* the plugin is loaded.
*
*
* In the plugin.yml, this entry is named load
.
*
* Example:
load: STARTUP
*
* @return the phase when the plugin should be loaded
*/
public PluginLoadOrder getLoad() {
return order;
}
/**
* Gives the list of authors for the plugin.
*
* - Gives credit to the developer.
*
- Used in some server error messages to provide helpful feedback on
* who to contact when an error occurs.
*
- A bukkit.org forum handle or email address is recommended.
*
- Is displayed when a user types
/version PluginName
* authors
must be in YAML list
* format.
*
*
* In the plugin.yml, this has two entries, author
and
* authors
.
*
* Single author example:
*
author: CaptainInflamo
* Multiple author example:
* authors: [Cogito, verrier, EvilSeph]
* When both are specified, author will be the first entry in the list, so
* this example:
* author: Grum
* authors:
* - feildmaster
* - amaranth
* Is equivilant to this example:
* authors: [Grum, feildmaster, aramanth]
*
* @return an immutable list of the plugin's authors
*/
public List getAuthors() {
return authors;
}
/**
* Gives the plugin's or plugin's author's website.
*
* - A link to the Curse page that includes documentation and downloads
* is highly recommended.
*
- Displayed when a user types
/version PluginName
*
*
* In the plugin.yml, this entry is named website
.
*
* Example:
*
website: http://www.curse.com/server-mods/minecraft/myplugin
*
* @return description of this plugin, or null if not specified
*/
public String getWebsite() {
return website;
}
/**
* Gives if the plugin uses a database.
*
* - Using a database is non-trivial.
*
- Valid values include
true
and false
*
*
* In the plugin.yml, this entry is named database
.
*
* Example:
*
database: false
*
* @return if this plugin requires a database
* @see Plugin#getDatabase()
*/
public boolean isDatabaseEnabled() {
return database;
}
public void setDatabaseEnabled(boolean database) {
this.database = database;
}
/**
* Gives a list of other plugins that the plugin requires.
*
* - Use the value in the {@link #getName()} of the target plugin to
* specify the dependency.
*
- If any plugin listed here is not found, your plugin will fail to
* load at startup.
*
- If multiple plugins list each other in
depend
,
* creating a network with no individual plugin does not list another
* plugin in the network,
* all plugins in that network will fail.
* depend
must be in must be in YAML list
* format.
*
*
* In the plugin.yml, this entry is named depend
.
*
* Example:
*
depend:
* - OnePlugin
* - AnotherPlugin
*
* @return immutable list of the plugin's dependencies
*/
public List getDepend() {
return depend;
}
/**
* Gives a list of other plugins that the plugin requires for full
* functionality. The {@link PluginManager} will make best effort to treat
* all entries here as if they were a {@link #getDepend() dependency}, but
* will never fail because of one of these entries.
*
* - Use the value in the {@link #getName()} of the target plugin to
* specify the dependency.
*
- When an unresolvable plugin is listed, it will be ignored and does
* not affect load order.
*
- When a circular dependency occurs (a network of plugins depending
* or soft-dependending each other), it will arbitrarily choose a
* plugin that can be resolved when ignoring soft-dependencies.
*
softdepend
must be in YAML list
* format.
*
*
* In the plugin.yml, this entry is named softdepend
.
*
* Example:
*
softdepend: [OnePlugin, AnotherPlugin]
*
* @return immutable list of the plugin's preferred dependencies
*/
public List getSoftDepend() {
return softDepend;
}
/**
* Gets the list of plugins that should consider this plugin a
* soft-dependency.
*
* - Use the value in the {@link #getName()} of the target plugin to
* specify the dependency.
*
- The plugin should load before any other plugins listed here.
*
- Specifying another plugin here is strictly equivalent to having the
* specified plugin's {@link #getSoftDepend()} include {@link
* #getName() this plugin}.
*
loadbefore
must be in YAML list
* format.
*
*
* In the plugin.yml, this entry is named loadbefore
.
*
* Example:
*
loadbefore:
* - OnePlugin
* - AnotherPlugin
*
* @return immutable list of plugins that should consider this plugin a
* soft-dependency
*/
public List getLoadBefore() {
return loadBefore;
}
/**
* Gives the token to prefix plugin-specific logging messages with.
*
* - This includes all messages using {@link Plugin#getLogger()}.
*
- If not specified, the server uses the plugin's {@link #getName()
* name}.
*
- This should clearly indicate what plugin is being logged.
*
*
* In the plugin.yml, this entry is named prefix
.
*
* Example:
prefix: ex-why-zee
*
* @return the prefixed logging token, or null if not specified
*/
public String getPrefix() {
return prefix;
}
/**
* Gives the map of command-name to command-properties. Each entry in this
* map corresponds to a single command and the respective values are the
* properties of the command. Each property, with the exception of
* aliases, can be defined at runtime using methods in {@link
* PluginCommand} and are defined here only as a convenience.
*
* The command section's description
*
* Node
* Method
* Type
* Description
* Example
*
* description
* {@link PluginCommand#setDescription(String)}
* String
* A user-friendly description for a command. It is useful for
* documentation purposes as well as in-game help.
* description: Set yourself on fire
*
* aliases
* {@link PluginCommand#setAliases(List)}
* String or List of
* strings
* Alternative command names, with special usefulness for commands
* that are already registered. Aliases are not effective when
* defined at runtime, so the plugin description file is the
* only way to have them properly defined.
*
* Note: Command aliases may not have a colon in them.
* Single alias format:
* aliases: combust_me
or
* multiple alias format:
* aliases: [combust_me, combustMe]
*
* permission
* {@link PluginCommand#setPermission(String)}
* String
* The name of the {@link Permission} required to use the command.
* A user without the permission will receive the specified
* message (see {@linkplain
* PluginCommand#setPermissionMessage(String) below}), or a
* standard one if no specific message is defined. Without the
* permission node, no {@link
* PluginCommand#setExecutor(CommandExecutor) CommandExecutor} or
* {@link PluginCommand#setTabCompleter(TabCompleter)
* TabCompleter} will be called.
* permission: inferno.flagrate
*
* permission-message
* {@link PluginCommand#setPermissionMessage(String)}
* String
*
* - Displayed to a player that attempts to use a command, but
* does not have the required permission. See {@link
* PluginCommand#getPermission() above}.
*
- <permission> is a macro that is replaced with the
* permission node required to use the command.
*
- Using empty quotes is a valid way to indicate nothing
* should be displayed to a player.
*
* permission-message: You do not have /<permission>
*
* usage
* {@link PluginCommand#setUsage(String)}
* String
* This message is displayed to a player when the {@link
* PluginCommand#setExecutor(CommandExecutor)} {@linkplain
* CommandExecutor#onCommand(CommandSender, Command, String, String[])
* returns false}. <command> is a macro that is replaced
* the command issued.
* usage: Syntax error! Perhaps you meant /<command> PlayerName?
* It is worth noting that to use a colon in a yaml, like
* `usage: Usage: /god [player]'
, you need to
* surround
* the message with double-quote:
* usage: "Usage: /god [player]"
*
*
* The commands are structured as a hiearchy of nested mappings.
* The primary (top-level, no intendentation) node is
* `commands
', while each individual command name is
* indented, indicating it maps to some value (in our case, the
* properties of the table above).
*
* Here is an example bringing together the piecemeal examples above, as
* well as few more definitions:
* commands:
* flagrate:
* description: Set yourself on fire.
* aliases: [combust_me, combustMe]
* permission: inferno.flagrate
* permission-message: You do not have /<permission>
* usage: Syntax error! Perhaps you meant /<command> PlayerName?
* burningdeaths:
* description: List how many times you have died by fire.
* aliases:
* - burning_deaths
* - burningDeaths
* permission: inferno.burningdeaths
* usage: |
* /<command> [player]
* Example: /<command> - see how many times you have burned to death
* Example: /<command> CaptainIce - see how many times CaptainIce has burned to death
* # The next command has no description, aliases, etc. defined, but is still valid
* # Having an empty declaration is useful for defining the description, permission, and messages from a configuration dynamically
* apocalypse:
*
* Note: Command names may not have a colon in their name.
*
* @return the commands this plugin will register
*/
public Map> getCommands() {
return commands;
}
/**
* Gives the list of permissions the plugin will register at runtime,
* immediately proceding enabling. The format for defining permissions is
* a map from permission name to properties. To represent a map without
* any specific property, empty curly-braces (
* {}
) may be used (as a null value is not
* accepted, unlike the {@link #getCommands() commands} above).
*
* A list of optional properties for permissions:
*
* The permission section's description
*
* Node
* Description
* Example
*
* description
* Plaintext (user-friendly) description of what the permission
* is for.
* description: Allows you to set yourself on fire
*
* default
* The default state for the permission, as defined by {@link
* Permission#getDefault()}. If not defined, it will be set to
* the value of {@link PluginDescriptionFile#getPermissionDefault()}.
*
* For reference:
* true
- Represents a positive assignment to
* {@link Permissible permissibles}.
* false
- Represents no assignment to {@link
* Permissible permissibles}.
* op
- Represents a positive assignment to
* {@link Permissible#isOp() operator permissibles}.
* notop
- Represents a positive assignment to
* {@link Permissible#isOp() non-operator permissibiles}.
*
* default: true
*
* children
* Allows other permissions to be set as a {@linkplain
* Permission#getChildren() relation} to the parent permission.
* When a parent permissions is assigned, child permissions are
* respectively assigned as well.
*
* - When a parent permission is assigned negatively, child
* permissions are assigned based on an inversion of their
* association.
*
- When a parent permission is assigned positively, child
* permissions are assigned based on their association.
*
*
* Child permissions may be defined in a number of ways:
* - Children may be defined as a list of
* names. Using a list will treat all children associated
* positively to their parent.
*
- Children may be defined as a map. Each permission name maps
* to either a boolean (representing the association), or a
* nested permission definition (just as another permission).
* Using a nested definition treats the child as a positive
* association.
*
- A nested permission definition must be a map of these same
* properties. To define a valid nested permission without
* defining any specific property, empty curly-braces (
*
{}
) must be used.
* - A nested permission may carry it's own nested permissions
* as children, as they may also have nested permissions, and
* so forth. There is no direct limit to how deep the
* permission tree is defined.
*
* As a list:
* children: [inferno.flagrate, inferno.burningdeaths]
* Or as a mapping:
* children:
* inferno.flagrate: true
* inferno.burningdeaths: true
* An additional example showing basic nested values can be seen
* here.
*
*
*
* The permissions are structured as a hiearchy of nested mappings.
* The primary (top-level, no intendentation) node is
* `permissions
', while each individual permission name is
* indented, indicating it maps to some value (in our case, the
* properties of the table above).
*
* Here is an example using some of the properties:
* permissions:
* inferno.*:
* description: Gives access to all Inferno commands
* children:
* inferno.flagrate: true
* inferno.burningdeaths: true
* inferno.flagate:
* description: Allows you to ignite yourself
* default: true
* inferno.burningdeaths:
* description: Allows you to see how many times you have burned to death
* default: true
*
* Another example, with nested definitions, can be found here.
*
* @return the permissions this plugin will register
*/
public List getPermissions() {
if (permissions == null) {
if (lazyPermissions == null) {
permissions = ImmutableList.of();
} else {
permissions = ImmutableList.copyOf(Permission.loadPermissions(lazyPermissions, "Permission node '%s' in plugin description file for " + getFullName() + " is invalid", defaultPerm));
lazyPermissions = null;
}
}
return permissions;
}
/**
* Gives the default {@link Permission#getDefault() default} state of
* {@link #getPermissions() permissions} registered for the plugin.
*
* - If not specified, it will be {@link PermissionDefault#OP}.
*
- It is matched using {@link PermissionDefault#getByName(String)}
*
- It only affects permissions that do not define the
*
default
node.
* - It may be any value in {@link PermissionDefault}.
*
*
* In the plugin.yml, this entry is named default-permission
.
*
* Example:
default-permission: NOT_OP
*
* @return the default value for the plugin's permissions
*/
public PermissionDefault getPermissionDefault() {
return defaultPerm;
}
/**
* Gives a set of every {@link PluginAwareness} for a plugin. An awareness
* dictates something that a plugin developer acknowledges when the plugin
* is compiled. Some implementions may define extra awarenesses that are
* not included in the API. Any unrecognized
* awareness (one unsupported or in a future version) will cause a dummy
* object to be created instead of failing.
*
*
* - Currently only supports the enumerated values in {@link
* PluginAwareness.Flags}.
*
- Each awareness starts the identifier with bang-at
* (
!@
).
* - Unrecognized (future / unimplemented) entries are quietly replaced
* by a generic object that implements PluginAwareness.
*
- A type of awareness must be defined by the runtime and acknowledged
* by the API, effectively discluding any derived type from any
* plugin's classpath.
*
awareness
must be in YAML list
* format.
*
*
* In the plugin.yml, this entry is named awareness
.
*
* Example:
awareness:
* - !@UTF8
*
* Note: Although unknown versions of some future awareness are
* gracefully substituted, previous versions of Bukkit (ones prior to the
* first implementation of awareness) will fail to load a plugin that
* defines any awareness.
*
* @return a set containing every awareness for the plugin
*/
public Set getAwareness() {
return awareness;
}
/**
* Returns the name of a plugin, including the version. This method is
* provided for convenience; it uses the {@link #getName()} and {@link
* #getVersion()} entries.
*
* @return a descriptive name of the plugin and respective version
*/
public String getFullName() {
return name + " v" + version;
}
/**
* @return unused
* @deprecated unused
*/
@Deprecated
public String getClassLoaderOf() {
return classLoaderOf;
}
/**
* Saves this PluginDescriptionFile to the given writer
*
* @param writer Writer to output this file to
*/
public void save(Writer writer) {
YAML.get().dump(saveMap(), writer);
}
private void loadMap(Map, ?> map) throws InvalidDescriptionException {
try {
name = rawName = map.get("name").toString();
if (!name.matches("^[A-Za-z0-9 _.-]+$")) {
throw new InvalidDescriptionException("name '" + name + "' contains invalid characters.");
}
name = name.replace(' ', '_');
} catch (NullPointerException ex) {
throw new InvalidDescriptionException(ex, "name is not defined");
} catch (ClassCastException ex) {
throw new InvalidDescriptionException(ex, "name is of wrong type");
}
try {
version = map.get("version").toString();
} catch (NullPointerException ex) {
throw new InvalidDescriptionException(ex, "version is not defined");
} catch (ClassCastException ex) {
throw new InvalidDescriptionException(ex, "version is of wrong type");
}
try {
main = map.get("main").toString();
if (main.startsWith("org.bukkit.")) {
throw new InvalidDescriptionException("main may not be within the org.bukkit namespace");
}
} catch (NullPointerException ex) {
throw new InvalidDescriptionException(ex, "main is not defined");
} catch (ClassCastException ex) {
throw new InvalidDescriptionException(ex, "main is of wrong type");
}
if (map.get("commands") != null) {
ImmutableMap.Builder> commandsBuilder = ImmutableMap.builder();
try {
for (Map.Entry, ?> command : ((Map, ?>) map.get("commands")).entrySet()) {
ImmutableMap.Builder commandBuilder = ImmutableMap.builder();
if (command.getValue() != null) {
for (Map.Entry, ?> commandEntry : ((Map, ?>) command.getValue()).entrySet()) {
if (commandEntry.getValue() instanceof Iterable) {
// This prevents internal alias list changes
ImmutableList.Builder