You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

PluginMetaData.java 21KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636
  1. /*
  2. * Copyright (c) 2006-2013 DMDirc Developers
  3. *
  4. * Permission is hereby granted, free of charge, to any person obtaining a copy
  5. * of this software and associated documentation files (the "Software"), to deal
  6. * in the Software without restriction, including without limitation the rights
  7. * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  8. * copies of the Software, and to permit persons to whom the Software is
  9. * furnished to do so, subject to the following conditions:
  10. *
  11. * The above copyright notice and this permission notice shall be included in
  12. * all copies or substantial portions of the Software.
  13. *
  14. * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  15. * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  16. * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  17. * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  18. * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  19. * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
  20. * SOFTWARE.
  21. */
  22. package com.dmdirc.plugins;
  23. import com.dmdirc.updater.Version;
  24. import com.dmdirc.util.io.ConfigFile;
  25. import com.dmdirc.util.io.InvalidConfigFileException;
  26. import com.dmdirc.util.io.StreamUtils;
  27. import com.dmdirc.util.resourcemanager.ResourceManager;
  28. import java.io.File;
  29. import java.io.IOException;
  30. import java.io.InputStream;
  31. import java.net.URL;
  32. import java.util.ArrayList;
  33. import java.util.Collection;
  34. import java.util.Collections;
  35. import java.util.HashMap;
  36. import java.util.List;
  37. import java.util.Map;
  38. /**
  39. * Reads metadata for a plugin. Plugin metadata is defined in a DMDirc
  40. * {@link ConfigFile} which is typically found inside plugin jars as
  41. * <code>META-INF/plugin.config</code>. The following sections are read from
  42. * the config file:
  43. * <ul>
  44. * <li><code>metadata</code> - generic plugin information. See {@link #readMetaData(java.util.Map)}.
  45. * <li><code>version</code> - versioning information. See {@link #readVersion(java.util.Map)}.
  46. * <li><code>defaults</code> - optional default settings to add to the configuration
  47. * <li><code>formatters</code> - optional default formatters to add to the configuration
  48. * <li><code>icons</code> - optional default icons to add to the configuration
  49. * <li><code>requires</code> - optional generic requirements. See {@link #readRequirements(java.util.Map, java.util.List)}.
  50. * <li><code>required-services</code> - optional service requirements. See {@link #readRequirements(java.util.Map, java.util.List)}.
  51. * <li><code>updates</code> - optional information for automated updates. See {@link #readUpdates(java.util.Map)}.
  52. * <li><code>provides</code> - optional list of services provided by the plugin. See {@link #readProvides(java.util.List)}.
  53. * <li><code>exports</code> - optional list of methods exported by the plugin. See {@link #readExports(java.util.List)}.
  54. * <li><code>persistent</code> - optional list of persistent classes within the plugin. See {@link #readPersistent(java.util.List)}.
  55. * </ul>
  56. * A collection of errors that occurred when attempting to read the metadata
  57. * is available via the {@link #getErrors()} method.
  58. *
  59. * @since 0.6.6
  60. */
  61. public class PluginMetaData {
  62. /** A collection of errors that occurred when reading or validating the data. */
  63. private final Collection<String> errors = new ArrayList<>();
  64. /** Default settings defined in the plugin metadata. */
  65. private final Map<String, String> defaultSettings = new HashMap<>();
  66. /** Formatters defined in the plugin metadata. */
  67. private final Map<String, String> formatters = new HashMap<>();
  68. /** Icons defined in the plugin metadata. */
  69. private final Map<String, String> icons = new HashMap<>();
  70. /** A set of requirements made by the plugin. */
  71. private final Map<String, String> requirements = new HashMap<>();
  72. /** A list of required services. */
  73. private final Collection<String> requiredServices = new ArrayList<>();
  74. /** Services provided by this plugin. */
  75. private final Collection<String> services = new ArrayList<>();
  76. /** Methods exported by this plugin. */
  77. private final Collection<String> exports = new ArrayList<>();
  78. /** Persistent classes in this plugin. */
  79. private final Collection<String> persistentClasses = new ArrayList<>();
  80. /** The name of the parent plugin, if any. */
  81. private String parent;
  82. /** The name of the main class, if any. */
  83. private String mainClass;
  84. /** The friendly version name/number. */
  85. private String friendlyVersion;
  86. /** The version of the plugin. */
  87. private Version version;
  88. /** The ID to use with the updater system, if any. */
  89. private int updaterId;
  90. /** The author-supplied friendly name of the plugin. */
  91. private String friendlyName;
  92. /** The internal name of the plugin. */
  93. private String name;
  94. /** The name of the author of the plugin. */
  95. private String author;
  96. /** The author-supplied description of the plugin. */
  97. private String description;
  98. /** Whether or not the plugin is marked as unloadable. */
  99. private boolean unloadable;
  100. /** The URL to the plugin. */
  101. private final URL pluginUrl;
  102. /** The URL to load the metadata from. */
  103. private final URL url;
  104. /** The parent plugin manager. */
  105. private PluginManager manager;
  106. /**
  107. * Creates a new meta data reader for a config file at the specified URL.
  108. *
  109. * @param manager Plugin manager
  110. * @param url The URL to load the config file from
  111. * @param pluginUrl The URL to the plugin that this data corresponds to
  112. */
  113. public PluginMetaData(final PluginManager manager, final URL url, final URL pluginUrl) {
  114. this.manager = manager;
  115. this.pluginUrl = pluginUrl;
  116. this.url = url;
  117. }
  118. /**
  119. * Loads plugin metadata from a config file.
  120. */
  121. public void load() {
  122. errors.clear();
  123. InputStream stream = null;
  124. try {
  125. stream = getStream();
  126. final ConfigFile configFile = new ConfigFile(stream);
  127. configFile.read();
  128. readMetaData(configFile.getKeyDomain("metadata"));
  129. readVersion(configFile.getKeyDomain("version"));
  130. readUpdates(configFile.getKeyDomain("updates"));
  131. readSettings(defaultSettings, configFile.getKeyDomain("defaults"));
  132. readSettings(formatters, configFile.getKeyDomain("formatters"));
  133. readSettings(icons, configFile.getKeyDomain("icons"));
  134. readRequirements(configFile.getKeyDomain("requires"),
  135. configFile.getFlatDomain("required-services"));
  136. readProvides(configFile.getFlatDomain("provides"));
  137. readPersistent(configFile.getFlatDomain("persistent"));
  138. readExports(configFile.getFlatDomain("exports"));
  139. } catch (IOException | InvalidConfigFileException ex) {
  140. errors.add("Unable to read config file: " + ex.getMessage());
  141. StreamUtils.close(stream);
  142. }
  143. }
  144. /**
  145. * Get the InputStream for this PluginMetaData
  146. *
  147. * @return The InputStream for this PluginMetaData
  148. */
  149. private InputStream getStream() throws IOException {
  150. // Sometimes url.openStream sucks, and this breaks reloading, so try
  151. // not to use it if we have a jar file.
  152. if (url.toString().startsWith("jar:file:")) {
  153. final String[] bits = url.toString().replaceFirst("^jar:file:", "jar://").split("!", 2);
  154. if (bits.length > 1) {
  155. final String file = bits[1].replaceFirst("^/", "");
  156. final ResourceManager rm = ResourceManager.getResourceManager(bits[0]);
  157. final InputStream s = rm.getResourceInputStream(file);
  158. if (s == null) {
  159. throw new IOException("Unable to find " + file + " in " + bits[0]);
  160. }
  161. return s;
  162. }
  163. }
  164. return url.openStream();
  165. }
  166. /** {@inheritDoc} */
  167. @Override
  168. public boolean equals(final Object obj) {
  169. if (obj == null || getClass() != obj.getClass()) {
  170. return false;
  171. }
  172. final PluginMetaData other = (PluginMetaData) obj;
  173. return pluginUrl == other.getPluginUrl() || (pluginUrl != null
  174. && pluginUrl.equals(other.getPluginUrl()));
  175. }
  176. /** {@inheritDoc} */
  177. @Override
  178. public int hashCode() {
  179. return pluginUrl == null ? 0 : pluginUrl.hashCode();
  180. }
  181. // <editor-fold desc="Read methods">
  182. /**
  183. * Reads information from the metadata section of the config file.
  184. * The following entries are read from the metadata section:
  185. * <ul>
  186. * <li><code>mainclass</code> - full classname of the main class to load
  187. * <li><code>author</code> - name of the author
  188. * <li><code>description</code> - user-friendly description
  189. * <li><code>name</code> - internal short name
  190. * <li><code>nicename</code> - user-friendly name
  191. * <li><code>unloadable</code> - boolean indicating if the plugin can
  192. * be unloaded. Defaults to true if not specified.
  193. * </ul>
  194. * It is recommended that the <code>author</code> field should take the
  195. * form of "<code>name &lt;email@address&gt;</code>", although this is not
  196. * enforced.
  197. * <p>
  198. * The short name must be a single word (i.e., no spaces) that uniquely
  199. * identifies the plugin. This is typically the same as the plugin's jar
  200. * name.
  201. *
  202. * @param data A map of config entry names to values
  203. */
  204. protected void readMetaData(final Map<String, String> data) {
  205. if (data == null || data.isEmpty()) {
  206. errors.add("'metadata' section not specified or empty");
  207. return;
  208. }
  209. mainClass = data.get("mainclass");
  210. author = data.get("author");
  211. description = data.get("description");
  212. name = data.get("name");
  213. friendlyName = data.get("nicename");
  214. unloadable = !data.containsKey("unloadable")
  215. || !data.get("unloadable").matches("(?i)^true|1|yes$");
  216. }
  217. /**
  218. * Reads information from the version section of the config file.
  219. * The following entries are read from the version section:
  220. * <ul>
  221. * <li><code>friendly</code> - a user-friendly version string
  222. * <li><code>number</code> - a DMDirc {@link Version} 'number'
  223. * </ul>
  224. * If the 'number' field is not specified it will be defaulted to a value
  225. * of "0".
  226. *
  227. * @param data A map of config entry names to values
  228. */
  229. protected void readVersion(final Map<String, String> data) {
  230. if (data == null || data.isEmpty()) {
  231. errors.add("'version' section not specified or empty");
  232. return;
  233. }
  234. friendlyVersion = data.get("friendly");
  235. version = new Version(data.containsKey("number") ? data.get("number") : "0");
  236. }
  237. /**
  238. * Reads information from the updates section of the config file.
  239. * The following entries are read from the updates section:
  240. * <ul>
  241. * <li><code>id</code> - a numeric ID for the plugin's entry in the DMDirc
  242. * updater system
  243. * </ul>
  244. *
  245. * @param data A map of config entry names to values
  246. */
  247. protected void readUpdates(final Map<String, String> data) {
  248. updaterId = -1;
  249. if (data != null && data.containsKey("id")) {
  250. try {
  251. updaterId = Integer.parseInt(data.get("id"));
  252. } catch (NumberFormatException ex) {
  253. errors.add("Invalid updater id specified: " + data.get("id"));
  254. }
  255. }
  256. }
  257. /**
  258. * Copies the specified settings (defaults, formatters, icons, etc) into
  259. * the specified property.
  260. *
  261. * @param target The map to add settings to
  262. * @param data The settings specified in the metadata
  263. */
  264. protected void readSettings(final Map<String, String> target,
  265. final Map<String, String> data) {
  266. target.clear();
  267. if (data != null) {
  268. target.putAll(data);
  269. }
  270. }
  271. /**
  272. * Reads the requirements and required-services sections of the config file.
  273. * The requires section is a mapping of a requirement type to a constraint.
  274. * No validation is performed on this section, but the following types
  275. * are standard:
  276. * <ul>
  277. * <li><code>parent</code> - internal name of a single parent plugin,
  278. * e.g. 'ui_swing'.
  279. * <li><code>plugins</code> - a comma-separated list of plugins that are
  280. * required. Plugins are specified by their internal names, and each entry
  281. * may include an optional minimum and maximum version separated by colons,
  282. * e.g. 'ui_swing' or 'plugin1,plugin2:0.6.3,plugin3:0.1:0.2'.
  283. * <li><code>os</code> - one to three colon-separated regular expressions
  284. * to match the OS name, version and architecture, e.g. '.*win.*'.
  285. * <li><code>files</code> - a comma-separated list of files that must
  286. * exist. Each comma-separated value may contain multiple alternatives
  287. * separated by a pipe, e.g. '/bin/bash' or '/bin/bash|/bin/dash,/bin/foo'.
  288. * <li><code>dmdirc</code> - the minimum and maximum DMDirc versions,
  289. * separated by a '-', e.g. '0.6.3', '0.6.3-0.6.4', '-0.6.4'.
  290. * </ul>
  291. * The required-services section is a flat domain which lists services that
  292. * the plugin requires. No validation is performed on these values, but
  293. * each service should consist of a name and a type. The magic string
  294. * <code>any</code> may be specified in place of a name (e.g. 'any ui'
  295. * instead of 'swing ui').
  296. *
  297. * @param data The specified requirements
  298. * @param requiredServices The specified required services
  299. */
  300. protected void readRequirements(final Map<String, String> data,
  301. final List<String> requiredServices) {
  302. readSettings(requirements, data);
  303. if (requirements.containsKey("parent")) {
  304. parent = requirements.get("parent");
  305. } else {
  306. parent = null;
  307. }
  308. this.requiredServices.clear();
  309. if (requiredServices != null) {
  310. this.requiredServices.addAll(requiredServices);
  311. }
  312. }
  313. /**
  314. * Reads the provides section of the config file. This is a flat domain
  315. * containing a list of services provided by the plugin. Services are
  316. * specified as a space-separated name and type pair, e.g. 'swing ui' or
  317. * 'logging command'.
  318. *
  319. * @param services The services to be added
  320. */
  321. protected void readProvides(final List<String> services) {
  322. this.services.clear();
  323. if (services != null) {
  324. this.services.addAll(services);
  325. }
  326. }
  327. /**
  328. * Reads the persistent section of the config file. This is a flat domain
  329. * containing a list of classes which should be made persistent (i.e.,
  330. * loaded globally and not unloaded).
  331. *
  332. * @param classes The services to be added
  333. */
  334. protected void readPersistent(final List<String> classes) {
  335. this.persistentClasses.clear();
  336. if (classes != null) {
  337. this.persistentClasses.addAll(classes);
  338. }
  339. }
  340. /**
  341. * Reads the exports section of the config file. This is a flat domain
  342. * containing a list of exported methods in the format
  343. * <code>&lt;methodName&gt; in &lt;className&gt; [as &lt;methodAlias&gt;]</code>,
  344. * e.g. 'getFoo in my.class.name as getParser' or 'getParser in my.class.name'.
  345. *
  346. * @param exports The exported methods for this plugin
  347. */
  348. protected void readExports(final List<String> exports) {
  349. this.exports.clear();
  350. if (exports != null) {
  351. this.exports.addAll(exports);
  352. }
  353. }
  354. // </editor-fold>
  355. /**
  356. * Calculates the relative path of this plugin in relation to the main
  357. * plugin directory.
  358. *
  359. * @return The plugin's relative path, or absolute path if not within the
  360. * plugins directory
  361. */
  362. public String getRelativeFilename() {
  363. // Yuck...
  364. final String filename = getPluginUrl().getPath();
  365. final String dir = new File(manager.getDirectory())
  366. .getAbsolutePath() + File.separator;
  367. final String file = new File(filename).getAbsolutePath();
  368. return file.startsWith(dir) ? filename.substring(dir.length()) : filename;
  369. }
  370. // <editor-fold defaultstate="collapsed" desc="Getters">
  371. /**
  372. * What plugin manager owns this metadata?
  373. *
  374. * @return The pluginmanager that created this metadata.
  375. */
  376. public PluginManager getManager() {
  377. return manager;
  378. }
  379. /**
  380. * Retrieves the URL to the plugin that this metadata corresponds to.
  381. *
  382. * @return The plugin's URL
  383. */
  384. public URL getPluginUrl() {
  385. return pluginUrl;
  386. }
  387. /**
  388. * Retrieves a collection of errors that occurred while trying to read
  389. * the metadata.
  390. *
  391. * @return A (possibly empty) collection of errors that occurred.
  392. */
  393. public Collection<String> getErrors() {
  394. return Collections.unmodifiableCollection(errors);
  395. }
  396. /**
  397. * Determines whether or not there were errors encountered while trying to
  398. * read the metadata.
  399. *
  400. * @return True if there are errors, false otherwise
  401. * @see #getErrors()
  402. */
  403. public boolean hasErrors() {
  404. return !errors.isEmpty();
  405. }
  406. /**
  407. * Retrieves the author information for the plugin.
  408. *
  409. * @return The plugin's specified author
  410. */
  411. public String getAuthor() {
  412. return author;
  413. }
  414. /**
  415. * Retrieves a user-friendly description of the plugin.
  416. *
  417. * @return The plugin's user-friendly description
  418. */
  419. public String getDescription() {
  420. return description;
  421. }
  422. /**
  423. * Retrieves a user-friendly name of the plugin.
  424. *
  425. * @return This plugin's user-friendly name
  426. */
  427. public String getFriendlyName() {
  428. return friendlyName;
  429. }
  430. /**
  431. * Retrieves the specified main class of the plugin.
  432. *
  433. * @return This plugin's main class
  434. */
  435. public String getMainClass() {
  436. return mainClass;
  437. }
  438. /**
  439. * Retrieves the internal short name of this plugin.
  440. *
  441. * @return This plugin's internal short name
  442. */
  443. public String getName() {
  444. return name;
  445. }
  446. /**
  447. * Retrieves the user-friendly version of this plugin.
  448. *
  449. * @return This plugin's user-friendly version
  450. */
  451. public String getFriendlyVersion() {
  452. return friendlyVersion;
  453. }
  454. /**
  455. * Determines whether the plugin is specified as unloadable or not.
  456. *
  457. * @return True if the plugin is unloadable, false otherwise
  458. */
  459. public boolean isUnloadable() {
  460. return unloadable;
  461. }
  462. /**
  463. * Retrieves the version of this plugin
  464. *
  465. * @return This plugin's version
  466. */
  467. public Version getVersion() {
  468. return version;
  469. }
  470. /**
  471. * Retrieves the updater ID of this plugin.
  472. *
  473. * @return This plugin's updater ID, or -1 if not specified
  474. */
  475. public int getUpdaterId() {
  476. return updaterId;
  477. }
  478. /**
  479. * Returns a map of default settings supplied by this plugin.
  480. *
  481. * @return A map of setting keys to values
  482. */
  483. public Map<String, String> getDefaultSettings() {
  484. return Collections.unmodifiableMap(defaultSettings);
  485. }
  486. /**
  487. * Returns a map of default formatters supplied by this plugin.
  488. *
  489. * @return A map of formatter names to values
  490. */
  491. public Map<String, String> getFormatters() {
  492. return Collections.unmodifiableMap(formatters);
  493. }
  494. /**
  495. * Returns a map of default icons supplied by this plugin.
  496. *
  497. * @return A map of icon names to values
  498. */
  499. public Map<String, String> getIcons() {
  500. return Collections.unmodifiableMap(icons);
  501. }
  502. /**
  503. * Retrieves this plugin's parent, if specified.
  504. *
  505. * @return The desired parent plugin
  506. */
  507. public String getParent() {
  508. return parent;
  509. }
  510. /**
  511. * Retrieves the collection of required services.
  512. *
  513. * @see #readRequirements(java.util.Map, java.util.List)
  514. * @return A collection of plugin-defined service requirements
  515. */
  516. public Collection<String> getRequiredServices() {
  517. return Collections.unmodifiableCollection(requiredServices);
  518. }
  519. /**
  520. * Retrieves the collection of plugin requirements.
  521. *
  522. * @see #readRequirements(java.util.Map, java.util.List)
  523. * @return A collection of plugin-defined requirements
  524. */
  525. public Map<String, String> getRequirements() {
  526. return Collections.unmodifiableMap(requirements);
  527. }
  528. /**
  529. * Retrieves the collection of services exported by the plugin.
  530. *
  531. * @return The services which the plugin specifies it provides
  532. */
  533. public Collection<String> getServices() {
  534. return Collections.unmodifiableCollection(services);
  535. }
  536. /**
  537. * Retrieves the collection of methods exported by the plugin.
  538. *
  539. * @return The methods exported by the plugin
  540. */
  541. public Collection<String> getExports() {
  542. return Collections.unmodifiableCollection(exports);
  543. }
  544. /**
  545. * Retrieves the set of classes marked as persistent for this plugin.
  546. *
  547. * @return The classes of this plugin marked as persistent
  548. */
  549. public Collection<String> getPersistentClasses() {
  550. return Collections.unmodifiableCollection(persistentClasses);
  551. }
  552. // </editor-fold>
  553. }