123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636 |
- /*
- * Copyright (c) 2006-2013 DMDirc Developers
- *
- * Permission is hereby granted, free of charge, to any person obtaining a copy
- * of this software and associated documentation files (the "Software"), to deal
- * in the Software without restriction, including without limitation the rights
- * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- * copies of the Software, and to permit persons to whom the Software is
- * furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included in
- * all copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
- * SOFTWARE.
- */
-
- package com.dmdirc.plugins;
-
- import com.dmdirc.updater.Version;
- import com.dmdirc.util.io.ConfigFile;
- import com.dmdirc.util.io.InvalidConfigFileException;
- import com.dmdirc.util.io.StreamUtils;
- import com.dmdirc.util.resourcemanager.ResourceManager;
-
- import java.io.File;
- import java.io.IOException;
- import java.io.InputStream;
- import java.net.URL;
- import java.util.ArrayList;
- import java.util.Collection;
- import java.util.Collections;
- import java.util.HashMap;
- import java.util.List;
- import java.util.Map;
-
- /**
- * Reads metadata for a plugin. Plugin metadata is defined in a DMDirc
- * {@link ConfigFile} which is typically found inside plugin jars as
- * <code>META-INF/plugin.config</code>. The following sections are read from
- * the config file:
- * <ul>
- * <li><code>metadata</code> - generic plugin information. See {@link #readMetaData(java.util.Map)}.
- * <li><code>version</code> - versioning information. See {@link #readVersion(java.util.Map)}.
- * <li><code>defaults</code> - optional default settings to add to the configuration
- * <li><code>formatters</code> - optional default formatters to add to the configuration
- * <li><code>icons</code> - optional default icons to add to the configuration
- * <li><code>requires</code> - optional generic requirements. See {@link #readRequirements(java.util.Map, java.util.List)}.
- * <li><code>required-services</code> - optional service requirements. See {@link #readRequirements(java.util.Map, java.util.List)}.
- * <li><code>updates</code> - optional information for automated updates. See {@link #readUpdates(java.util.Map)}.
- * <li><code>provides</code> - optional list of services provided by the plugin. See {@link #readProvides(java.util.List)}.
- * <li><code>exports</code> - optional list of methods exported by the plugin. See {@link #readExports(java.util.List)}.
- * <li><code>persistent</code> - optional list of persistent classes within the plugin. See {@link #readPersistent(java.util.List)}.
- * </ul>
- * A collection of errors that occurred when attempting to read the metadata
- * is available via the {@link #getErrors()} method.
- *
- * @since 0.6.6
- */
- public class PluginMetaData {
-
- /** A collection of errors that occurred when reading or validating the data. */
- private final Collection<String> errors = new ArrayList<>();
-
- /** Default settings defined in the plugin metadata. */
- private final Map<String, String> defaultSettings = new HashMap<>();
-
- /** Formatters defined in the plugin metadata. */
- private final Map<String, String> formatters = new HashMap<>();
-
- /** Icons defined in the plugin metadata. */
- private final Map<String, String> icons = new HashMap<>();
-
- /** A set of requirements made by the plugin. */
- private final Map<String, String> requirements = new HashMap<>();
-
- /** A list of required services. */
- private final Collection<String> requiredServices = new ArrayList<>();
-
- /** Services provided by this plugin. */
- private final Collection<String> services = new ArrayList<>();
-
- /** Methods exported by this plugin. */
- private final Collection<String> exports = new ArrayList<>();
-
- /** Persistent classes in this plugin. */
- private final Collection<String> persistentClasses = new ArrayList<>();
-
- /** The name of the parent plugin, if any. */
- private String parent;
-
- /** The name of the main class, if any. */
- private String mainClass;
-
- /** The friendly version name/number. */
- private String friendlyVersion;
-
- /** The version of the plugin. */
- private Version version;
-
- /** The ID to use with the updater system, if any. */
- private int updaterId;
-
- /** The author-supplied friendly name of the plugin. */
- private String friendlyName;
-
- /** The internal name of the plugin. */
- private String name;
-
- /** The name of the author of the plugin. */
- private String author;
-
- /** The author-supplied description of the plugin. */
- private String description;
-
- /** Whether or not the plugin is marked as unloadable. */
- private boolean unloadable;
-
- /** The URL to the plugin. */
- private final URL pluginUrl;
-
- /** The URL to load the metadata from. */
- private final URL url;
-
- /** The parent plugin manager. */
- private PluginManager manager;
-
- /**
- * Creates a new meta data reader for a config file at the specified URL.
- *
- * @param manager Plugin manager
- * @param url The URL to load the config file from
- * @param pluginUrl The URL to the plugin that this data corresponds to
- */
- public PluginMetaData(final PluginManager manager, final URL url, final URL pluginUrl) {
- this.manager = manager;
- this.pluginUrl = pluginUrl;
- this.url = url;
- }
-
- /**
- * Loads plugin metadata from a config file.
- */
- public void load() {
- errors.clear();
- InputStream stream = null;
-
- try {
- stream = getStream();
- final ConfigFile configFile = new ConfigFile(stream);
- configFile.read();
- readMetaData(configFile.getKeyDomain("metadata"));
- readVersion(configFile.getKeyDomain("version"));
- readUpdates(configFile.getKeyDomain("updates"));
- readSettings(defaultSettings, configFile.getKeyDomain("defaults"));
- readSettings(formatters, configFile.getKeyDomain("formatters"));
- readSettings(icons, configFile.getKeyDomain("icons"));
- readRequirements(configFile.getKeyDomain("requires"),
- configFile.getFlatDomain("required-services"));
- readProvides(configFile.getFlatDomain("provides"));
- readPersistent(configFile.getFlatDomain("persistent"));
- readExports(configFile.getFlatDomain("exports"));
- } catch (IOException | InvalidConfigFileException ex) {
- errors.add("Unable to read config file: " + ex.getMessage());
- StreamUtils.close(stream);
- }
- }
-
- /**
- * Get the InputStream for this PluginMetaData
- *
- * @return The InputStream for this PluginMetaData
- */
- private InputStream getStream() throws IOException {
- // Sometimes url.openStream sucks, and this breaks reloading, so try
- // not to use it if we have a jar file.
-
- if (url.toString().startsWith("jar:file:")) {
- final String[] bits = url.toString().replaceFirst("^jar:file:", "jar://").split("!", 2);
-
- if (bits.length > 1) {
- final String file = bits[1].replaceFirst("^/", "");
- final ResourceManager rm = ResourceManager.getResourceManager(bits[0]);
- final InputStream s = rm.getResourceInputStream(file);
- if (s == null) {
- throw new IOException("Unable to find " + file + " in " + bits[0]);
- }
- return s;
- }
- }
-
- return url.openStream();
- }
-
- /** {@inheritDoc} */
- @Override
- public boolean equals(final Object obj) {
- if (obj == null || getClass() != obj.getClass()) {
- return false;
- }
-
- final PluginMetaData other = (PluginMetaData) obj;
- return pluginUrl == other.getPluginUrl() || (pluginUrl != null
- && pluginUrl.equals(other.getPluginUrl()));
- }
-
- /** {@inheritDoc} */
- @Override
- public int hashCode() {
- return pluginUrl == null ? 0 : pluginUrl.hashCode();
- }
-
- // <editor-fold desc="Read methods">
-
- /**
- * Reads information from the metadata section of the config file.
- * The following entries are read from the metadata section:
- * <ul>
- * <li><code>mainclass</code> - full classname of the main class to load
- * <li><code>author</code> - name of the author
- * <li><code>description</code> - user-friendly description
- * <li><code>name</code> - internal short name
- * <li><code>nicename</code> - user-friendly name
- * <li><code>unloadable</code> - boolean indicating if the plugin can
- * be unloaded. Defaults to true if not specified.
- * </ul>
- * It is recommended that the <code>author</code> field should take the
- * form of "<code>name <email@address></code>", although this is not
- * enforced.
- * <p>
- * The short name must be a single word (i.e., no spaces) that uniquely
- * identifies the plugin. This is typically the same as the plugin's jar
- * name.
- *
- * @param data A map of config entry names to values
- */
- protected void readMetaData(final Map<String, String> data) {
- if (data == null || data.isEmpty()) {
- errors.add("'metadata' section not specified or empty");
- return;
- }
-
- mainClass = data.get("mainclass");
- author = data.get("author");
- description = data.get("description");
- name = data.get("name");
- friendlyName = data.get("nicename");
- unloadable = !data.containsKey("unloadable")
- || !data.get("unloadable").matches("(?i)^true|1|yes$");
- }
-
- /**
- * Reads information from the version section of the config file.
- * The following entries are read from the version section:
- * <ul>
- * <li><code>friendly</code> - a user-friendly version string
- * <li><code>number</code> - a DMDirc {@link Version} 'number'
- * </ul>
- * If the 'number' field is not specified it will be defaulted to a value
- * of "0".
- *
- * @param data A map of config entry names to values
- */
- protected void readVersion(final Map<String, String> data) {
- if (data == null || data.isEmpty()) {
- errors.add("'version' section not specified or empty");
- return;
- }
-
- friendlyVersion = data.get("friendly");
- version = new Version(data.containsKey("number") ? data.get("number") : "0");
- }
-
- /**
- * Reads information from the updates section of the config file.
- * The following entries are read from the updates section:
- * <ul>
- * <li><code>id</code> - a numeric ID for the plugin's entry in the DMDirc
- * updater system
- * </ul>
- *
- * @param data A map of config entry names to values
- */
- protected void readUpdates(final Map<String, String> data) {
- updaterId = -1;
-
- if (data != null && data.containsKey("id")) {
- try {
- updaterId = Integer.parseInt(data.get("id"));
- } catch (NumberFormatException ex) {
- errors.add("Invalid updater id specified: " + data.get("id"));
- }
- }
- }
-
- /**
- * Copies the specified settings (defaults, formatters, icons, etc) into
- * the specified property.
- *
- * @param target The map to add settings to
- * @param data The settings specified in the metadata
- */
- protected void readSettings(final Map<String, String> target,
- final Map<String, String> data) {
- target.clear();
-
- if (data != null) {
- target.putAll(data);
- }
- }
-
- /**
- * Reads the requirements and required-services sections of the config file.
- * The requires section is a mapping of a requirement type to a constraint.
- * No validation is performed on this section, but the following types
- * are standard:
- * <ul>
- * <li><code>parent</code> - internal name of a single parent plugin,
- * e.g. 'ui_swing'.
- * <li><code>plugins</code> - a comma-separated list of plugins that are
- * required. Plugins are specified by their internal names, and each entry
- * may include an optional minimum and maximum version separated by colons,
- * e.g. 'ui_swing' or 'plugin1,plugin2:0.6.3,plugin3:0.1:0.2'.
- * <li><code>os</code> - one to three colon-separated regular expressions
- * to match the OS name, version and architecture, e.g. '.*win.*'.
- * <li><code>files</code> - a comma-separated list of files that must
- * exist. Each comma-separated value may contain multiple alternatives
- * separated by a pipe, e.g. '/bin/bash' or '/bin/bash|/bin/dash,/bin/foo'.
- * <li><code>dmdirc</code> - the minimum and maximum DMDirc versions,
- * separated by a '-', e.g. '0.6.3', '0.6.3-0.6.4', '-0.6.4'.
- * </ul>
- * The required-services section is a flat domain which lists services that
- * the plugin requires. No validation is performed on these values, but
- * each service should consist of a name and a type. The magic string
- * <code>any</code> may be specified in place of a name (e.g. 'any ui'
- * instead of 'swing ui').
- *
- * @param data The specified requirements
- * @param requiredServices The specified required services
- */
- protected void readRequirements(final Map<String, String> data,
- final List<String> requiredServices) {
- readSettings(requirements, data);
-
- if (requirements.containsKey("parent")) {
- parent = requirements.get("parent");
- } else {
- parent = null;
- }
-
- this.requiredServices.clear();
-
- if (requiredServices != null) {
- this.requiredServices.addAll(requiredServices);
- }
- }
-
- /**
- * Reads the provides section of the config file. This is a flat domain
- * containing a list of services provided by the plugin. Services are
- * specified as a space-separated name and type pair, e.g. 'swing ui' or
- * 'logging command'.
- *
- * @param services The services to be added
- */
- protected void readProvides(final List<String> services) {
- this.services.clear();
-
- if (services != null) {
- this.services.addAll(services);
- }
- }
-
- /**
- * Reads the persistent section of the config file. This is a flat domain
- * containing a list of classes which should be made persistent (i.e.,
- * loaded globally and not unloaded).
- *
- * @param classes The services to be added
- */
- protected void readPersistent(final List<String> classes) {
- this.persistentClasses.clear();
-
- if (classes != null) {
- this.persistentClasses.addAll(classes);
- }
- }
-
- /**
- * Reads the exports section of the config file. This is a flat domain
- * containing a list of exported methods in the format
- * <code><methodName> in <className> [as <methodAlias>]</code>,
- * e.g. 'getFoo in my.class.name as getParser' or 'getParser in my.class.name'.
- *
- * @param exports The exported methods for this plugin
- */
- protected void readExports(final List<String> exports) {
- this.exports.clear();
-
- if (exports != null) {
- this.exports.addAll(exports);
- }
- }
-
- // </editor-fold>
-
- /**
- * Calculates the relative path of this plugin in relation to the main
- * plugin directory.
- *
- * @return The plugin's relative path, or absolute path if not within the
- * plugins directory
- */
- public String getRelativeFilename() {
- // Yuck...
- final String filename = getPluginUrl().getPath();
- final String dir = new File(manager.getDirectory())
- .getAbsolutePath() + File.separator;
- final String file = new File(filename).getAbsolutePath();
-
- return file.startsWith(dir) ? filename.substring(dir.length()) : filename;
- }
-
- // <editor-fold defaultstate="collapsed" desc="Getters">
-
- /**
- * What plugin manager owns this metadata?
- *
- * @return The pluginmanager that created this metadata.
- */
- public PluginManager getManager() {
- return manager;
- }
-
- /**
- * Retrieves the URL to the plugin that this metadata corresponds to.
- *
- * @return The plugin's URL
- */
- public URL getPluginUrl() {
- return pluginUrl;
- }
-
- /**
- * Retrieves a collection of errors that occurred while trying to read
- * the metadata.
- *
- * @return A (possibly empty) collection of errors that occurred.
- */
- public Collection<String> getErrors() {
- return Collections.unmodifiableCollection(errors);
- }
-
- /**
- * Determines whether or not there were errors encountered while trying to
- * read the metadata.
- *
- * @return True if there are errors, false otherwise
- * @see #getErrors()
- */
- public boolean hasErrors() {
- return !errors.isEmpty();
- }
-
- /**
- * Retrieves the author information for the plugin.
- *
- * @return The plugin's specified author
- */
- public String getAuthor() {
- return author;
- }
-
- /**
- * Retrieves a user-friendly description of the plugin.
- *
- * @return The plugin's user-friendly description
- */
- public String getDescription() {
- return description;
- }
-
- /**
- * Retrieves a user-friendly name of the plugin.
- *
- * @return This plugin's user-friendly name
- */
- public String getFriendlyName() {
- return friendlyName;
- }
-
- /**
- * Retrieves the specified main class of the plugin.
- *
- * @return This plugin's main class
- */
- public String getMainClass() {
- return mainClass;
- }
-
- /**
- * Retrieves the internal short name of this plugin.
- *
- * @return This plugin's internal short name
- */
- public String getName() {
- return name;
- }
-
- /**
- * Retrieves the user-friendly version of this plugin.
- *
- * @return This plugin's user-friendly version
- */
- public String getFriendlyVersion() {
- return friendlyVersion;
- }
-
- /**
- * Determines whether the plugin is specified as unloadable or not.
- *
- * @return True if the plugin is unloadable, false otherwise
- */
- public boolean isUnloadable() {
- return unloadable;
- }
-
- /**
- * Retrieves the version of this plugin
- *
- * @return This plugin's version
- */
- public Version getVersion() {
- return version;
- }
-
- /**
- * Retrieves the updater ID of this plugin.
- *
- * @return This plugin's updater ID, or -1 if not specified
- */
- public int getUpdaterId() {
- return updaterId;
- }
-
- /**
- * Returns a map of default settings supplied by this plugin.
- *
- * @return A map of setting keys to values
- */
- public Map<String, String> getDefaultSettings() {
- return Collections.unmodifiableMap(defaultSettings);
- }
-
- /**
- * Returns a map of default formatters supplied by this plugin.
- *
- * @return A map of formatter names to values
- */
- public Map<String, String> getFormatters() {
- return Collections.unmodifiableMap(formatters);
- }
-
- /**
- * Returns a map of default icons supplied by this plugin.
- *
- * @return A map of icon names to values
- */
- public Map<String, String> getIcons() {
- return Collections.unmodifiableMap(icons);
- }
-
- /**
- * Retrieves this plugin's parent, if specified.
- *
- * @return The desired parent plugin
- */
- public String getParent() {
- return parent;
- }
-
- /**
- * Retrieves the collection of required services.
- *
- * @see #readRequirements(java.util.Map, java.util.List)
- * @return A collection of plugin-defined service requirements
- */
- public Collection<String> getRequiredServices() {
- return Collections.unmodifiableCollection(requiredServices);
- }
-
- /**
- * Retrieves the collection of plugin requirements.
- *
- * @see #readRequirements(java.util.Map, java.util.List)
- * @return A collection of plugin-defined requirements
- */
- public Map<String, String> getRequirements() {
- return Collections.unmodifiableMap(requirements);
- }
-
- /**
- * Retrieves the collection of services exported by the plugin.
- *
- * @return The services which the plugin specifies it provides
- */
- public Collection<String> getServices() {
- return Collections.unmodifiableCollection(services);
- }
-
- /**
- * Retrieves the collection of methods exported by the plugin.
- *
- * @return The methods exported by the plugin
- */
- public Collection<String> getExports() {
- return Collections.unmodifiableCollection(exports);
- }
-
- /**
- * Retrieves the set of classes marked as persistent for this plugin.
- *
- * @return The classes of this plugin marked as persistent
- */
- public Collection<String> getPersistentClasses() {
- return Collections.unmodifiableCollection(persistentClasses);
- }
-
- // </editor-fold>
-
- }
|