Você não pode selecionar mais de 25 tópicos Os tópicos devem começar com uma letra ou um número, podem incluir traços ('-') e podem ter até 35 caracteres.

PluginInfo.java 55KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544
  1. /*
  2. * Copyright (c) 2006-2010 Chris Smith, Shane Mc Cormack, Gregory Holmes
  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.actions.ActionManager;
  24. import com.dmdirc.actions.CoreActionType;
  25. import com.dmdirc.config.Identity;
  26. import com.dmdirc.config.IdentityManager;
  27. import com.dmdirc.config.prefs.validator.ValidationResponse;
  28. import com.dmdirc.util.resourcemanager.ResourceManager;
  29. import com.dmdirc.util.ConfigFile;
  30. import com.dmdirc.util.InvalidConfigFileException;
  31. import com.dmdirc.logger.Logger;
  32. import com.dmdirc.logger.ErrorLevel;
  33. import com.dmdirc.updater.Version;
  34. import java.io.File;
  35. import java.io.IOException;
  36. import java.io.InputStream;
  37. import java.lang.reflect.Constructor;
  38. import java.lang.reflect.InvocationTargetException;
  39. import java.util.List;
  40. import java.util.Properties;
  41. import java.util.Map;
  42. import java.util.HashMap;
  43. import java.util.ArrayList;
  44. import java.util.Timer;
  45. import java.util.TimerTask;
  46. import java.net.URL;
  47. public class PluginInfo implements Comparable<PluginInfo>, ServiceProvider {
  48. /** A logger for this class. */
  49. private static final java.util.logging.Logger LOGGER = java.util.logging.Logger.getLogger(PluginInfo.class.getName());
  50. /** Plugin Meta Data */
  51. private ConfigFile metaData = null;
  52. /** URL that this plugin was loaded from */
  53. private final URL url;
  54. /** Filename for this plugin (taken from URL) */
  55. private final String filename;
  56. /** The actual Plugin from this jar */
  57. private Plugin plugin = null;
  58. /** The classloader used for this Plugin */
  59. private PluginClassLoader classloader = null;
  60. /** The resource manager used by this pluginInfo */
  61. private ResourceManager myResourceManager = null;
  62. /** Is this plugin only loaded temporarily? */
  63. private boolean tempLoaded = false;
  64. /** List of classes this plugin has */
  65. private List<String> myClasses = new ArrayList<String>();
  66. /** Requirements error message. */
  67. private String requirementsError = "";
  68. /** Last Error Message. */
  69. private String lastError = "No Error";
  70. /** Are we trying to load? */
  71. private boolean isLoading = false;
  72. /** Is this plugin using a migrated config? */
  73. private boolean migrated = false;
  74. /** List of services we provide. */
  75. private final List<Service> provides = new ArrayList<Service>();
  76. /** List of children of this plugin. */
  77. private final List<PluginInfo> children = new ArrayList<PluginInfo>();
  78. /** Map of exports */
  79. private final Map<String, ExportInfo> exports = new HashMap<String, ExportInfo>();
  80. /**
  81. * Create a new PluginInfo.
  82. *
  83. * @param url URL to file that this plugin is stored in.
  84. * @throws PluginException if there is an error loading the Plugin
  85. * @since 0.6
  86. */
  87. public PluginInfo(final URL url) throws PluginException {
  88. this(url, true);
  89. }
  90. /**
  91. * Create a new PluginInfo.
  92. *
  93. * @param url URL to file that this plugin is stored in.
  94. * @param load Should this plugin be loaded, or is this just a placeholder? (true for load, false for placeholder)
  95. * @throws PluginException if there is an error loading the Plugin
  96. * @since 0.6
  97. */
  98. public PluginInfo(final URL url, final boolean load) throws PluginException {
  99. this.url = url;
  100. this.filename = (new File(url.getPath())).getName();
  101. ResourceManager res;
  102. // Check for updates.
  103. if (new File(getFullFilename() + ".update").exists() && new File(getFullFilename()).delete()) {
  104. new File(getFullFilename() + ".update").renameTo(new File(getFullFilename()));
  105. }
  106. if (!load) {
  107. // Load the metaData if available.
  108. try {
  109. metaData = getConfigFile();
  110. } catch (IOException ioe) {
  111. metaData = null;
  112. }
  113. return;
  114. }
  115. try {
  116. res = getResourceManager();
  117. } catch (IOException ioe) {
  118. lastError = "Error with resourcemanager: " + ioe.getMessage();
  119. throw new PluginException("Plugin " + filename + " failed to load. " + lastError, ioe);
  120. }
  121. try {
  122. metaData = getConfigFile();
  123. if (metaData == null) {
  124. lastError = "plugin.config doesn't exist in jar";
  125. throw new PluginException("Plugin " + filename + " failed to load. " + lastError);
  126. }
  127. } catch (IOException e) {
  128. lastError = "plugin.config IOException: " + e.getMessage();
  129. throw new PluginException("Plugin " + filename + " failed to load, plugin.config failed to open - " + e.getMessage(), e);
  130. } catch (IllegalArgumentException e) {
  131. lastError = "plugin.config IllegalArgumentException: " + e.getMessage();
  132. throw new PluginException("Plugin " + filename + " failed to load, plugin.config failed to open - " + e.getMessage(), e);
  133. }
  134. if (!getVersion().isValid()) {
  135. lastError = "Incomplete plugin.config (Missing or invalid 'version')";
  136. throw new PluginException("Plugin " + filename + " failed to load. " + lastError);
  137. } else if (getAuthor().isEmpty()) {
  138. lastError = "Incomplete plugin.config (Missing or invalid 'author')";
  139. throw new PluginException("Plugin " + filename + " failed to load. " + lastError);
  140. } else if (getName().isEmpty() || getName().indexOf(' ') != -1) {
  141. lastError = "Incomplete plugin.config (Missing or invalid 'name')";
  142. throw new PluginException("Plugin " + filename + " failed to load. " + lastError);
  143. } else if (getMainClass().isEmpty()) {
  144. lastError = "Incomplete plugin.config (Missing or invalid 'mainclass')";
  145. throw new PluginException("Plugin " + filename + " failed to load. " + lastError);
  146. }
  147. if (checkRequirements(true)) {
  148. final String mainClass = getMainClass().replace('.', '/') + ".class";
  149. if (!res.resourceExists(mainClass)) {
  150. lastError = "main class file (" + mainClass + ") not found in jar.";
  151. throw new PluginException("Plugin " + filename + " failed to load. " + lastError);
  152. }
  153. for (final String classfilename : res.getResourcesStartingWith("")) {
  154. String classname = classfilename.replace('/', '.');
  155. if (classname.matches("^.*\\.class$")) {
  156. classname = classname.replaceAll("\\.class$", "");
  157. myClasses.add(classname);
  158. }
  159. }
  160. if (isPersistent() && loadAll()) {
  161. loadEntirePlugin();
  162. }
  163. } else {
  164. lastError = "One or more requirements not met (" + requirementsError + ")";
  165. throw new PluginException("Plugin " + filename + " was not loaded. " + lastError);
  166. }
  167. updateProvides();
  168. getDefaults();
  169. }
  170. /**
  171. * Get misc meta-information.
  172. *
  173. * @param properties The properties file to look in
  174. * @param metainfo The metainfos to look for in order. If the first item in
  175. * the array is not found, the next will be looked for, and
  176. * so on until either one is found, or none are found.
  177. * @param fallback Fallback value if requested values are not found
  178. * @return Misc Meta Info (or "" if none are found);
  179. */
  180. private String getMetaInfo(final Properties properties, final String[] metainfo, final String fallback) {
  181. for (String meta : metainfo) {
  182. String result = properties.getProperty(meta);
  183. if (result != null) {
  184. return result;
  185. }
  186. }
  187. return fallback;
  188. }
  189. /**
  190. * Return a ConfigFile that has been migrated from a Properties file.
  191. *
  192. * @return ConfigFile object with data from plugin.info properties file
  193. */
  194. private ConfigFile getMigratedConfigFile() throws IOException {
  195. final ResourceManager res = getResourceManager();
  196. final ConfigFile file = new ConfigFile(res.getResourceInputStream("META-INF/plugin.config"));
  197. migrated = true;
  198. // Logger.userError(ErrorLevel.LOW, "Plugin '"+getFilename()+"' is using an older plugin.info file, check for updates.");
  199. final Properties old = new Properties();
  200. old.load(res.getResourceInputStream("META-INF/plugin.info"));
  201. final Map<String, String> meta = new HashMap<String, String>();
  202. final Map<String, String> requires = new HashMap<String, String>();
  203. final Map<String, String> updates = new HashMap<String, String>();
  204. final Map<String, String> version = new HashMap<String, String>();
  205. final Map<String, String> misc = new HashMap<String, String>();
  206. final List<String> persistent = new ArrayList<String>();
  207. final List<String> provides = new ArrayList<String>();
  208. final List<String> required_services = new ArrayList<String>();
  209. meta.put("name", old.getProperty("name", ""));
  210. meta.put("author", old.getProperty("author", ""));
  211. meta.put("description", old.getProperty("description", ""));
  212. meta.put("mainclass", old.getProperty("mainclass", ""));
  213. if (old.containsKey("nicename")) {
  214. meta.put("nicename", old.getProperty("nicename", ""));
  215. }
  216. if (old.containsKey("loadall")) {
  217. meta.put("loadall", old.getProperty("loadall", "no"));
  218. }
  219. requires.put("os", getMetaInfo(old, new String[]{"required-os", "require-os"}, ""));
  220. requires.put("files", getMetaInfo(old, new String[]{"required-files", "require-files", "required-files", "require-files"}, ""));
  221. requires.put("plugins", getMetaInfo(old, new String[]{"required-plugins", "require-plugins", "required-plugin", "require-plugin"}, ""));
  222. requires.put("ui", getMetaInfo(old, new String[]{"required-ui", "require-ui"}, ""));
  223. requires.put("dmdirc", old.getProperty("minversion", "0") + "-" + old.getProperty("maxversion", ""));
  224. if (old.containsKey("addonid")) {
  225. updates.put("id", old.getProperty("addonid", ""));
  226. }
  227. version.put("number", old.getProperty("version", "0"));
  228. if (old.containsKey("friendlyversion")) {
  229. version.put("friendly", old.getProperty("friendlyversion", ""));
  230. }
  231. final boolean hasPersistent = old.containsKey("persistent");
  232. if (hasPersistent) {
  233. persistent.add("*");
  234. }
  235. for (Map.Entry entry : old.entrySet()) {
  236. final String key = entry.getKey().toString();
  237. final String value = entry.getValue().toString();
  238. // For compatability reasons, add the contents of the file to the "misc"
  239. // key section, to allow getMetaInfo() compatability for old files.
  240. misc.put(key, value);
  241. // Also handle persistent items here
  242. if (!hasPersistent && key.toLowerCase().startsWith("persistent-")) {
  243. persistent.add(key.substring(11));
  244. }
  245. }
  246. file.addDomain("metadata", meta);
  247. file.addDomain("requires", requires);
  248. file.addDomain("updates", updates);
  249. file.addDomain("version", version);
  250. file.addDomain("misc", misc);
  251. file.addDomain("persistent", persistent);
  252. file.addDomain("provides", provides);
  253. file.addDomain("required-services", required_services);
  254. return file;
  255. }
  256. /**
  257. * Get a ConfigFile object for this plugin.
  258. * This will load a ConfigFile
  259. *
  260. * @return the ConfigFile object for this plugin, or null if the plugin has no config
  261. * @throws IOException if there is an error with the ResourceManager.
  262. */
  263. private ConfigFile getConfigFile() throws IOException {
  264. ConfigFile file = null;
  265. final ResourceManager res = getResourceManager();
  266. if (res.resourceExists("META-INF/plugin.config")) {
  267. try {
  268. file = new ConfigFile(res.getResourceInputStream("META-INF/plugin.config"));
  269. file.read();
  270. } catch (InvalidConfigFileException icfe) {
  271. if (res.resourceExists("META-INF/plugin.info")) {
  272. file = getMigratedConfigFile();
  273. } else {
  274. throw new IOException("Unable to read plugin.config", icfe);
  275. }
  276. }
  277. } else if (res.resourceExists("META-INF/plugin.info")) {
  278. file = getMigratedConfigFile();
  279. }
  280. return file;
  281. }
  282. /**
  283. * Get the license for this plugin if it exists.
  284. *
  285. * @return An InputStream for the license of this plugin, or null if no
  286. * license found.
  287. * @throws IOException if there is an error with the ResourceManager.
  288. */
  289. public InputStream getLicenseStream() throws IOException {
  290. final ResourceManager res = getResourceManager();
  291. if (res.resourceExists("META-INF/license.txt")) {
  292. return res.getResourceInputStream("META-INF/license.txt");
  293. }
  294. return null;
  295. }
  296. /**
  297. * Get the defaults, formatters and icons for this plugin.
  298. */
  299. private void getDefaults() {
  300. if (metaData == null) {
  301. return;
  302. }
  303. final Identity defaults = IdentityManager.getAddonIdentity();
  304. final String domain = "plugin-" + getName();
  305. LOGGER.finer(getName() + ": Using domain '" + domain + "'");
  306. if (metaData.isKeyDomain("defaults")) {
  307. final Map<String, String> keysection = metaData.getKeyDomain("defaults");
  308. for (Map.Entry entry : keysection.entrySet()) {
  309. final String key = entry.getKey().toString();
  310. final String value = entry.getValue().toString();
  311. defaults.setOption(domain, key, value);
  312. }
  313. }
  314. if (metaData.isKeyDomain("formatters")) {
  315. final Map<String, String> keysection = metaData.getKeyDomain("formatters");
  316. for (Map.Entry entry : keysection.entrySet()) {
  317. final String key = entry.getKey().toString();
  318. final String value = entry.getValue().toString();
  319. defaults.setOption("formatter", key, value);
  320. }
  321. }
  322. if (metaData.isKeyDomain("icons")) {
  323. final Map<String, String> keysection = metaData.getKeyDomain("icons");
  324. for (Map.Entry entry : keysection.entrySet()) {
  325. final String key = entry.getKey().toString();
  326. final String value = entry.getValue().toString();
  327. defaults.setOption("icon", key, value);
  328. }
  329. }
  330. }
  331. /**
  332. * Update provides list.
  333. */
  334. private void updateProvides() {
  335. // Remove us from any existing provides lists.
  336. for (Service service : provides) {
  337. service.delProvider(this);
  338. }
  339. provides.clear();
  340. // Get services provided by this plugin
  341. final List<String> providesList = metaData.getFlatDomain("provides");
  342. if (providesList != null) {
  343. for (String item : providesList) {
  344. final String[] bits = item.split(" ");
  345. final String name = bits[0];
  346. final String type = (bits.length > 1) ? bits[1] : "misc";
  347. if (!name.equalsIgnoreCase("any") && !type.equalsIgnoreCase("export")) {
  348. final Service service = PluginManager.getPluginManager().getService(type, name, true);
  349. service.addProvider(this);
  350. provides.add(service);
  351. }
  352. }
  353. }
  354. updateExports();
  355. }
  356. /**
  357. * Called when the plugin is updated using the updater.
  358. * Reloads metaData and updates the list of files.
  359. */
  360. public void pluginUpdated() {
  361. try {
  362. // Force a new resourcemanager just incase.
  363. final ResourceManager res = getResourceManager(true);
  364. myClasses.clear();
  365. for (final String classfilename : res.getResourcesStartingWith("")) {
  366. String classname = classfilename.replace('/', '.');
  367. if (classname.matches("^.*\\.class$")) {
  368. classname = classname.replaceAll("\\.class$", "");
  369. myClasses.add(classname);
  370. }
  371. }
  372. updateMetaData();
  373. updateProvides();
  374. getDefaults();
  375. } catch (IOException ioe) {
  376. }
  377. }
  378. /**
  379. * Check if this plugin was migrated or not.
  380. *
  381. * @return true if the plugins config file was a plugin.info not a plugin.config
  382. */
  383. public boolean isMigrated() {
  384. return migrated;
  385. }
  386. /**
  387. * Try to reload the metaData from the plugin.config file.
  388. * If this fails, the old data will be used still.
  389. *
  390. * @return true if metaData was reloaded ok, else false.
  391. */
  392. private boolean updateMetaData() {
  393. // Force a new resourcemanager just incase.
  394. try {
  395. final ResourceManager res = getResourceManager(true);
  396. final ConfigFile newMetaData = getConfigFile();
  397. if (newMetaData != null) {
  398. metaData = newMetaData;
  399. return true;
  400. }
  401. } catch (IOException ioe) {
  402. }
  403. return false;
  404. }
  405. /**
  406. * Get the contents of requirementsError
  407. *
  408. * @return requirementsError
  409. */
  410. public String getRequirementsError() {
  411. return requirementsError;
  412. }
  413. /**
  414. * Gets a resource manager for this plugin
  415. *
  416. * @return The resource manager for this plugin
  417. * @throws IOException if there is any problem getting a ResourceManager for this plugin
  418. */
  419. public synchronized ResourceManager getResourceManager() throws IOException {
  420. return getResourceManager(false);
  421. }
  422. /**
  423. * Get the resource manager for this plugin
  424. *
  425. * @return The resource manager for this plugin
  426. * @param forceNew Force a new resource manager rather than using the old one.
  427. * @throws IOException if there is any problem getting a ResourceManager for this plugin
  428. * @since 0.6
  429. */
  430. public synchronized ResourceManager getResourceManager(final boolean forceNew) throws IOException {
  431. if (myResourceManager == null || forceNew) {
  432. myResourceManager = ResourceManager.getResourceManager("jar://" + getFullFilename());
  433. // Clear the resourcemanager in 10 seconds to stop us holding the file open
  434. new Timer(filename + "-resourcemanagerTimer").schedule(new TimerTask() {
  435. /** {@inheritDoc} */
  436. @Override
  437. public void run() {
  438. myResourceManager = null;
  439. }
  440. }, 10000);
  441. }
  442. return myResourceManager;
  443. }
  444. /**
  445. * Checks to see if the minimum version requirement of the plugin is
  446. * satisfied.
  447. * If either version is non-positive, the test passes.
  448. * On failure, the requirementsError field will contain a user-friendly
  449. * error message.
  450. *
  451. * @param desired The desired minimum version of DMDirc.
  452. * @param actual The actual current version of DMDirc.
  453. * @return True if the test passed, false otherwise
  454. */
  455. protected boolean checkMinimumVersion(final String desired, final int actual) {
  456. int idesired;
  457. if (desired.isEmpty()) {
  458. return true;
  459. }
  460. try {
  461. idesired = Integer.parseInt(desired);
  462. } catch (NumberFormatException ex) {
  463. requirementsError = "'minversion' is a non-integer";
  464. return false;
  465. }
  466. if (actual > 0 && idesired > 0 && actual < idesired) {
  467. requirementsError = "Plugin is for a newer version of DMDirc";
  468. return false;
  469. } else {
  470. return true;
  471. }
  472. }
  473. /**
  474. * Checks to see if the maximum version requirement of the plugin is
  475. * satisfied.
  476. * If either version is non-positive, the test passes.
  477. * If the desired version is empty, the test passes.
  478. * On failure, the requirementsError field will contain a user-friendly
  479. * error message.
  480. *
  481. * @param desired The desired maximum version of DMDirc.
  482. * @param actual The actual current version of DMDirc.
  483. * @return True if the test passed, false otherwise
  484. */
  485. protected boolean checkMaximumVersion(final String desired, final int actual) {
  486. int idesired;
  487. if (desired.isEmpty()) {
  488. return true;
  489. }
  490. try {
  491. idesired = Integer.parseInt(desired);
  492. } catch (NumberFormatException ex) {
  493. requirementsError = "'maxversion' is a non-integer";
  494. return false;
  495. }
  496. if (actual > 0 && idesired > 0 && actual > idesired) {
  497. requirementsError = "Plugin is for an older version of DMDirc";
  498. return false;
  499. } else {
  500. return true;
  501. }
  502. }
  503. /**
  504. * Checks to see if the OS requirements of the plugin are satisfied.
  505. * If the desired string is empty, the test passes.
  506. * Otherwise it is used as one to three colon-delimited regular expressions,
  507. * to test the name, version and architecture of the OS, respectively.
  508. * On failure, the requirementsError field will contain a user-friendly
  509. * error message.
  510. *
  511. * @param desired The desired OS requirements
  512. * @param actualName The actual name of the OS
  513. * @param actualVersion The actual version of the OS
  514. * @param actualArch The actual architecture of the OS
  515. * @return True if the test passes, false otherwise
  516. */
  517. protected boolean checkOS(final String desired, final String actualName, final String actualVersion, final String actualArch) {
  518. if (desired.isEmpty()) {
  519. return true;
  520. }
  521. final String[] desiredParts = desired.split(":");
  522. if (!actualName.toLowerCase().matches(desiredParts[0])) {
  523. requirementsError = "Invalid OS. (Wanted: '" + desiredParts[0] + "', actual: '" + actualName + "')";
  524. return false;
  525. } else if (desiredParts.length > 1 && !actualVersion.toLowerCase().matches(desiredParts[1])) {
  526. requirementsError = "Invalid OS version. (Wanted: '" + desiredParts[1] + "', actual: '" + actualVersion + "')";
  527. return false;
  528. } else if (desiredParts.length > 2 && !actualArch.toLowerCase().matches(desiredParts[2])) {
  529. requirementsError = "Invalid OS architecture. (Wanted: '" + desiredParts[2] + "', actual: '" + actualArch + "')";
  530. return false;
  531. }
  532. return true;
  533. }
  534. /**
  535. * Checks to see if the UI requirements of the plugin are satisfied.
  536. * If the desired string is empty, the test passes.
  537. * Otherwise it is used as a regular expressions against the package of the
  538. * UIController to test what UI is currently in use.
  539. * On failure, the requirementsError field will contain a user-friendly
  540. * error message.
  541. *
  542. * @param desired The desired UI requirements
  543. * @param actual The package of the current UI in use.
  544. * @return True if the test passes, false otherwise
  545. */
  546. protected boolean checkUI(final String desired, final String actual) {
  547. if (desired.isEmpty()) {
  548. return true;
  549. }
  550. if (!actual.toLowerCase().matches(desired)) {
  551. requirementsError = "Invalid UI. (Wanted: '" + desired + "', actual: '" + actual + "')";
  552. return false;
  553. }
  554. return true;
  555. }
  556. /**
  557. * Checks to see if the file requirements of the plugin are satisfied.
  558. * If the desired string is empty, the test passes.
  559. * Otherwise it is passed to File.exists() to see if the file is valid.
  560. * Multiple files can be specified by using a "," to separate. And either/or
  561. * files can be specified using a "|" (eg /usr/bin/bash|/bin/bash)
  562. * If the test fails, the requirementsError field will contain a
  563. * user-friendly error message.
  564. *
  565. * @param desired The desired file requirements
  566. * @return True if the test passes, false otherwise
  567. */
  568. protected boolean checkFiles(final String desired) {
  569. if (desired.isEmpty()) {
  570. return true;
  571. }
  572. for (String files : desired.split(",")) {
  573. final String[] filelist = files.split("\\|");
  574. boolean foundFile = false;
  575. for (String file : filelist) {
  576. if ((new File(file)).exists()) {
  577. foundFile = true;
  578. break;
  579. }
  580. }
  581. if (!foundFile) {
  582. requirementsError = "Required file '" + files + "' not found";
  583. return false;
  584. }
  585. }
  586. return true;
  587. }
  588. /**
  589. * Checks to see if the plugin requirements of the plugin are satisfied.
  590. * If the desired string is empty, the test passes.
  591. * Plugins should be specified as:
  592. * plugin1[:minversion[:maxversion]],plugin2[:minversion[:maxversion]]
  593. * Plugins will be attempted to be loaded if not loaded, else the test will
  594. * fail if the versions don't match, or the plugin isn't known.
  595. * If the test fails, the requirementsError field will contain a
  596. * user-friendly error message.
  597. *
  598. * @param desired The desired file requirements
  599. * @return True if the test passes, false otherwise
  600. */
  601. protected boolean checkPlugins(final String desired) {
  602. if (desired.isEmpty()) {
  603. return true;
  604. }
  605. for (String plugin : desired.split(",")) {
  606. final String[] data = plugin.split(":");
  607. final PluginInfo pi = PluginManager.getPluginManager().getPluginInfoByName(data[0]);
  608. if (pi == null) {
  609. requirementsError = "Required plugin '" + data[0] + "' was not found";
  610. return false;
  611. } else {
  612. if (data.length > 1) {
  613. // Check plugin minimum version matches.
  614. if (pi.getVersion().compareTo(new Version(data[1])) < 0) {
  615. requirementsError = "Plugin '" + data[0] + "' is too old (Required Version: " + data[1] + ", Actual Version: " + pi.getVersion() + ")";
  616. return false;
  617. } else {
  618. if (data.length > 2) {
  619. // Check plugin maximum version matches.
  620. if (pi.getVersion().compareTo(new Version(data[2])) > 0) {
  621. requirementsError = "Plugin '" + data[0] + "' is too new (Required Version: " + data[2] + ", Actual Version: " + pi.getVersion() + ")";
  622. return false;
  623. }
  624. }
  625. }
  626. }
  627. }
  628. }
  629. return true;
  630. }
  631. /**
  632. * Are the requirements for this plugin met?
  633. *
  634. * @param preliminary Is this a preliminary check?
  635. * @return true/false (Actual error if false is in the requirementsError field)
  636. */
  637. public boolean checkRequirements(final boolean preliminary) {
  638. if (metaData == null) {
  639. // No meta-data, so no requirements.
  640. return true;
  641. }
  642. /* final String uiPackage;
  643. if (Main.getUI().getClass().getPackage() != null) {
  644. uiPackage = Main.getUI().getClass().getPackage().getName();
  645. } else {
  646. final String uiController = Main.getUI().getClass().getName();
  647. if (uiController.lastIndexOf('.') >= 0) {
  648. uiPackage = uiController.substring(0,uiController.lastIndexOf('.'));
  649. } else {
  650. uiPackage = uiController;
  651. }
  652. } */
  653. if (/*!checkMinimumVersion(getMinVersion(), Main.SVN_REVISION) ||
  654. !checkMaximumVersion(getMaxVersion(), Main.SVN_REVISION) ||*/!checkOS(getKeyValue("requires", "os", ""), System.getProperty("os.name"), System.getProperty("os.version"), System.getProperty("os.arch")) ||
  655. !checkFiles(getKeyValue("requires", "files", "")) ||
  656. (!preliminary && !checkPlugins(getKeyValue("requires", "plugins", ""))) ||
  657. (!preliminary && !checkServices(metaData.getFlatDomain("required-services")))) {
  658. return false;
  659. }
  660. // All requirements passed, woo \o
  661. return true;
  662. }
  663. /**
  664. * Check if the services required by this plugin are available.
  665. *
  666. * @param services Required services
  667. * @return true if all services are available
  668. */
  669. private boolean checkServices(final List<String> services) {
  670. if (services == null || services.size() < 1) {
  671. return true;
  672. }
  673. for (String requirement : services) {
  674. boolean available = false;
  675. final String[] bits = requirement.split(" ");
  676. final String name = bits[0];
  677. final String type = (bits.length > 1) ? bits[1] : "misc";
  678. // System.out.println(toString()+" Looking for: "+requirement);
  679. Service service = null;
  680. if (name.equalsIgnoreCase("any")) {
  681. final List<Service> serviceList = PluginManager.getPluginManager().getServicesByType(type);
  682. if (serviceList.size() > 0) {
  683. // Default to the first Service in the list
  684. service = serviceList.get(0);
  685. if (serviceList.size() > 1) {
  686. // Check to see if any of the others are already active
  687. for (Service serv : serviceList) {
  688. if (service.isActive()) {
  689. // Already active, abort.
  690. available = true;
  691. }
  692. }
  693. }
  694. }
  695. } else {
  696. service = PluginManager.getPluginManager().getService(type, name, false);
  697. }
  698. // System.out.println("\tSatisfied by: "+service+" "+(PluginInfo)service.getActiveProvider());
  699. if (service != null) {
  700. available = service.activate();
  701. }
  702. if (!available) {
  703. return false;
  704. }
  705. }
  706. return true;
  707. }
  708. /**
  709. * Is this provider active at this time.
  710. *
  711. * @return true if the provider is able to provide its services
  712. */
  713. @Override
  714. public final boolean isActive() {
  715. return isLoaded();
  716. }
  717. /** Activate the services. */
  718. @Override
  719. public void activateServices() {
  720. loadPlugin();
  721. }
  722. /** {@inheritDoc} */
  723. @Override
  724. public String getProviderName() {
  725. return "Plugin: " + getNiceName() + " (" + getName() + " / " + getFilename() + ")";
  726. }
  727. /**
  728. * Get a list of services provided by this provider.
  729. *
  730. * @return A list of services provided by this provider.
  731. */
  732. @Override
  733. public List<Service> getServices() {
  734. return new ArrayList<Service>(provides);
  735. }
  736. /**
  737. * Is this plugin loaded?
  738. *
  739. * @return True if the plugin is currently (non-temporarily) loaded, false
  740. * otherwise
  741. */
  742. public boolean isLoaded() {
  743. return (plugin != null) && !tempLoaded;
  744. }
  745. /**
  746. * Is this plugin temporarily loaded?
  747. *
  748. * @return True if this plugin is currently temporarily loaded, false
  749. * otherwise
  750. */
  751. public boolean isTempLoaded() {
  752. return (plugin != null) && tempLoaded;
  753. }
  754. /**
  755. * Load entire plugin.
  756. * This loads all files in the jar immediately.
  757. *
  758. * @throws PluginException if there is an error with the resourcemanager
  759. */
  760. private void loadEntirePlugin() throws PluginException {
  761. // Load the main "Plugin" from the jar
  762. loadPlugin();
  763. // Now load all the rest.
  764. for (String classname : myClasses) {
  765. loadClass(classname);
  766. }
  767. }
  768. /**
  769. * Try to Load the plugin files temporarily.
  770. */
  771. public void loadPluginTemp() {
  772. tempLoaded = true;
  773. loadPlugin();
  774. }
  775. /**
  776. * Load any required plugins
  777. */
  778. public void loadRequired() {
  779. final String required = getKeyValue("requires", "plugins", "");
  780. for (String plugin : required.split(",")) {
  781. final String[] data = plugin.split(":");
  782. if (!data[0].trim().isEmpty()) {
  783. final PluginInfo pi = PluginManager.getPluginManager().getPluginInfoByName(data[0]);
  784. if (pi == null) {
  785. return;
  786. }
  787. if (tempLoaded) {
  788. pi.loadPluginTemp();
  789. } else {
  790. pi.loadPlugin();
  791. }
  792. }
  793. }
  794. }
  795. /**
  796. * Load the plugin files.
  797. */
  798. public void loadPlugin() {
  799. updateProvides();
  800. if (!checkRequirements(isTempLoaded() || tempLoaded)) {
  801. lastError = "Unable to loadPlugin, all requirements not met. (" + requirementsError + ")";
  802. return;
  803. }
  804. if (isTempLoaded()) {
  805. tempLoaded = false;
  806. loadRequired();
  807. try {
  808. plugin.onLoad();
  809. } catch (LinkageError e) {
  810. lastError = "Error in onLoad for " + getName() + ":" + e.getMessage();
  811. Logger.userError(ErrorLevel.MEDIUM, lastError, e);
  812. unloadPlugin();
  813. } catch (Exception e) {
  814. lastError = "Error in onLoad for " + getName() + ":" + e.getMessage();
  815. Logger.userError(ErrorLevel.MEDIUM, lastError, e);
  816. unloadPlugin();
  817. }
  818. } else {
  819. if (isLoaded() || metaData == null || isLoading) {
  820. lastError = "Not Loading: (" + isLoaded() + "||" + (metaData == null) + "||" + isLoading + ")";
  821. return;
  822. }
  823. isLoading = true;
  824. loadRequired();
  825. loadClass(getMainClass());
  826. if (isLoaded()) {
  827. ActionManager.processEvent(CoreActionType.PLUGIN_LOADED, null, this);
  828. }
  829. isLoading = false;
  830. }
  831. }
  832. /**
  833. * Add the given Plugin as a child of this plugin.
  834. *
  835. * @param child Child to add
  836. */
  837. public void addChild(final PluginInfo child) {
  838. children.add(child);
  839. }
  840. /**
  841. * Remove the given Plugin as a child of this plugin.
  842. *
  843. * @param child Child to remove
  844. */
  845. public void delChild(final PluginInfo child) {
  846. children.remove(child);
  847. }
  848. /**
  849. * Load the given classname.
  850. *
  851. * @param classname Class to load
  852. */
  853. private void loadClass(final String classname) {
  854. try {
  855. if (classloader == null) {
  856. if (getKeyValue("requires", "parent", "").isEmpty()) {
  857. classloader = new PluginClassLoader(this);
  858. } else {
  859. final String parentName = getKeyValue("requires", "parent", "");
  860. final PluginInfo pi = PluginManager.getPluginManager().getPluginInfoByName(parentName);
  861. if (pi == null) {
  862. lastError = "Required parent '" + parentName + "' was not found";
  863. return;
  864. } else {
  865. pi.addChild(this);
  866. classloader = pi.getPluginClassLoader().getSubClassLoader(this);
  867. }
  868. }
  869. }
  870. // Don't reload a class if its already loaded.
  871. if (classloader.isClassLoaded(classname, true)) {
  872. lastError = "Classloader says we are already loaded.";
  873. return;
  874. }
  875. final Class<?> c = classloader.loadClass(classname);
  876. final Constructor<?> constructor = c.getConstructor(new Class[]{});
  877. // Only try and construct the main class, anything else should be constructed
  878. // by the plugin itself.
  879. if (classname.equals(getMainClass())) {
  880. final Object temp = constructor.newInstance(new Object[]{});
  881. if (temp instanceof Plugin) {
  882. final ValidationResponse prerequisites = ((Plugin) temp).checkPrerequisites();
  883. if (!prerequisites.isFailure()) {
  884. plugin = (Plugin) temp;
  885. LOGGER.finer(getName() + ": Setting domain 'plugin-" + getName() + "'");
  886. plugin.setDomain("plugin-" + getName());
  887. if (!tempLoaded) {
  888. try {
  889. plugin.onLoad();
  890. } catch (LinkageError e) {
  891. lastError = "Error in onLoad for " + getName() + ":" + e.getMessage();
  892. Logger.userError(ErrorLevel.MEDIUM, lastError, e);
  893. unloadPlugin();
  894. } catch (Exception e) {
  895. lastError = "Error in onLoad for " + getName() + ":" + e.getMessage();
  896. Logger.userError(ErrorLevel.MEDIUM, lastError, e);
  897. unloadPlugin();
  898. }
  899. }
  900. } else {
  901. if (!tempLoaded) {
  902. lastError = "Prerequisites for plugin not met. ('" + filename + ":" + getMainClass() + "' -> '" + prerequisites.getFailureReason() + "') ";
  903. Logger.userError(ErrorLevel.LOW, lastError);
  904. }
  905. }
  906. }
  907. }
  908. } catch (ClassNotFoundException cnfe) {
  909. lastError = "Class not found ('" + filename + ":" + classname + ":" + classname.equals(getMainClass()) + "') - " + cnfe.getMessage();
  910. Logger.userError(ErrorLevel.LOW, lastError, cnfe);
  911. } catch (NoSuchMethodException nsme) {
  912. // Don't moan about missing constructors for any class thats not the main Class
  913. lastError = "Constructor missing ('" + filename + ":" + classname + ":" + classname.equals(getMainClass()) + "') - " + nsme.getMessage();
  914. if (classname.equals(getMainClass())) {
  915. Logger.userError(ErrorLevel.LOW, lastError, nsme);
  916. }
  917. } catch (IllegalAccessException iae) {
  918. lastError = "Unable to access constructor ('" + filename + ":" + classname + ":" + classname.equals(getMainClass()) + "') - " + iae.getMessage();
  919. Logger.userError(ErrorLevel.LOW, lastError, iae);
  920. } catch (InvocationTargetException ite) {
  921. lastError = "Unable to invoke target ('" + filename + ":" + classname + ":" + classname.equals(getMainClass()) + "') - " + ite.getMessage();
  922. Logger.userError(ErrorLevel.LOW, lastError, ite);
  923. } catch (InstantiationException ie) {
  924. lastError = "Unable to instantiate plugin ('" + filename + ":" + classname + ":" + classname.equals(getMainClass()) + "') - " + ie.getMessage();
  925. Logger.userError(ErrorLevel.LOW, lastError, ie);
  926. } catch (NoClassDefFoundError ncdf) {
  927. lastError = "Unable to instantiate plugin ('" + filename + ":" + classname + ":" + classname.equals(getMainClass()) + "') - Unable to find class: " + ncdf.getMessage();
  928. Logger.userError(ErrorLevel.LOW, lastError, ncdf);
  929. } catch (VerifyError ve) {
  930. lastError = "Unable to instantiate plugin ('" + filename + ":" + classname + ":" + classname.equals(getMainClass()) + "') - Incompatible: " + ve.getMessage();
  931. Logger.userError(ErrorLevel.LOW, lastError, ve);
  932. }
  933. }
  934. /**
  935. * Unload the plugin if possible.
  936. */
  937. public void unloadPlugin() {
  938. unloadPlugin(false);
  939. }
  940. /**
  941. * Can this plugin be unloaded?
  942. * Will return false if:
  943. * - The plugin is persistent (all its classes are loaded into the global class loader)
  944. * - The plugin isn't currently loaded
  945. * - The metadata key "unloadable" is set to false, no or 0
  946. *
  947. * @return true if plugin can be unloaded
  948. */
  949. public boolean isUnloadable() {
  950. if (isPersistent() || (!isLoaded() && !isTempLoaded())) {
  951. return false;
  952. } else {
  953. final String unloadable = getKeyValue("metadata", "unloadable", "true");
  954. return (unloadable.equalsIgnoreCase("yes") || unloadable.equalsIgnoreCase("true") || unloadable.equalsIgnoreCase("1"));
  955. }
  956. }
  957. /**
  958. * Unload the plugin if possible.
  959. *
  960. * @param parentUnloading is our parent already unloading? (if so, don't call delChild)
  961. */
  962. private void unloadPlugin(final boolean parentUnloading) {
  963. if (isUnloadable()) {
  964. if (!isTempLoaded()) {
  965. // Unload all children
  966. for (PluginInfo child : children) {
  967. child.unloadPlugin(true);
  968. }
  969. // Delete ourself as a child of our parent.
  970. if (!parentUnloading && !getKeyValue("requires", "parent", "").isEmpty()) {
  971. final String parentName = getKeyValue("requires", "parent", "");
  972. final PluginInfo pi = PluginManager.getPluginManager().getPluginInfoByName(parentName);
  973. if (pi != null) {
  974. pi.delChild(this);
  975. classloader = pi.getPluginClassLoader().getSubClassLoader(this);
  976. }
  977. }
  978. // Now unload ourself
  979. try {
  980. plugin.onUnload();
  981. } catch (Exception e) {
  982. lastError = "Error in onUnload for " + getName() + ":" + e + " - " + e.getMessage();
  983. Logger.userError(ErrorLevel.MEDIUM, lastError, e);
  984. e.printStackTrace();
  985. }
  986. ActionManager.processEvent(CoreActionType.PLUGIN_UNLOADED, null, this);
  987. for (Service service : provides) {
  988. service.delProvider(this);
  989. }
  990. provides.clear();
  991. }
  992. tempLoaded = false;
  993. plugin = null;
  994. classloader = null;
  995. }
  996. }
  997. /**
  998. * Get the last Error
  999. *
  1000. * @return last Error
  1001. * @since 0.6
  1002. */
  1003. public String getLastError() {
  1004. return lastError;
  1005. }
  1006. /**
  1007. * Get the list of Classes
  1008. *
  1009. * @return Classes this plugin has
  1010. */
  1011. public List<String> getClassList() {
  1012. return myClasses;
  1013. }
  1014. /**
  1015. * Get the value of the given key from the given keysection, or fallback.
  1016. *
  1017. * @param section Section to look in
  1018. * @param key Key to check
  1019. * @param fallback Value to use if key doesn't exist.
  1020. * @return Value of the key in the keysection, or the fallback if not present
  1021. */
  1022. public String getKeyValue(final String section, final String key, final String fallback) {
  1023. if (metaData != null && metaData.isKeyDomain(section)) {
  1024. final Map<String, String> keysection = metaData.getKeyDomain(section);
  1025. return keysection.containsKey(key) ? keysection.get(key) : fallback;
  1026. }
  1027. return fallback;
  1028. }
  1029. /**
  1030. * Get the main Class
  1031. *
  1032. * @return Main Class to begin loading.
  1033. */
  1034. public String getMainClass() {
  1035. return getKeyValue("metadata", "mainclass", "");
  1036. }
  1037. /**
  1038. * Get the Plugin for this plugin.
  1039. *
  1040. * @return Plugin
  1041. */
  1042. public Plugin getPlugin() {
  1043. return plugin;
  1044. }
  1045. /**
  1046. * Get the PluginClassLoader for this plugin.
  1047. *
  1048. * @return PluginClassLoader
  1049. */
  1050. protected PluginClassLoader getPluginClassLoader() {
  1051. return classloader;
  1052. }
  1053. /**
  1054. * Get the plugin friendly version
  1055. *
  1056. * @return Plugin friendly Version
  1057. */
  1058. public String getFriendlyVersion() {
  1059. return getKeyValue("version", "friendly", String.valueOf(getVersion()));
  1060. }
  1061. /**
  1062. * Get the plugin version
  1063. *
  1064. * @return Plugin Version
  1065. */
  1066. public Version getVersion() {
  1067. return new Version(getKeyValue("version", "number", "0"));
  1068. }
  1069. /**
  1070. * Get the id for this plugin on the addons site.
  1071. * If a plugin has been submitted to addons.dmdirc.com, and plugin.config
  1072. * contains a property addonid then this will return it.
  1073. * This is used along with the version property to allow the auto-updater to
  1074. * update the addon if the author submits a new version to the addons site.
  1075. *
  1076. * @return Addon Site ID number
  1077. * -1 If not present
  1078. * -2 If non-integer
  1079. */
  1080. public int getAddonID() {
  1081. try {
  1082. return Integer.parseInt(getKeyValue("updates", "id", "-1"));
  1083. } catch (NumberFormatException nfe) {
  1084. return -2;
  1085. }
  1086. }
  1087. /**
  1088. * Is this a persistent plugin?
  1089. *
  1090. * @return true if persistent, else false
  1091. */
  1092. public boolean isPersistent() {
  1093. if (metaData != null && metaData.isFlatDomain("persistent")) {
  1094. final List<String> items = metaData.getFlatDomain("persistent");
  1095. return items.contains("*");
  1096. }
  1097. return false;
  1098. }
  1099. /**
  1100. * Does this plugin contain any persistent classes?
  1101. *
  1102. * @return true if this plugin contains any persistent classes, else false
  1103. */
  1104. public boolean hasPersistent() {
  1105. if (metaData != null && metaData.isFlatDomain("persistent")) {
  1106. final List<String> items = metaData.getFlatDomain("persistent");
  1107. return !items.isEmpty();
  1108. }
  1109. return false;
  1110. }
  1111. /**
  1112. * Get a list of all persistent classes in this plugin
  1113. *
  1114. * @return List of all persistent classes in this plugin
  1115. */
  1116. public List<String> getPersistentClasses() {
  1117. final List<String> result = new ArrayList<String>();
  1118. if (isPersistent()) {
  1119. try {
  1120. ResourceManager res = getResourceManager();
  1121. for (final String filename : res.getResourcesStartingWith("")) {
  1122. if (filename.matches("^.*\\.class$")) {
  1123. result.add(filename.replaceAll("\\.class$", "").replace('/', '.'));
  1124. }
  1125. }
  1126. } catch (IOException e) {
  1127. // Jar no longer exists?
  1128. }
  1129. } else if (metaData != null && metaData.isFlatDomain("persistent")) {
  1130. return metaData.getFlatDomain("persistent");
  1131. }
  1132. return result;
  1133. }
  1134. /**
  1135. * Is this a persistent class?
  1136. *
  1137. * @param classname class to check persistence of
  1138. * @return true if file (or whole plugin) is persistent, else false
  1139. */
  1140. public boolean isPersistent(final String classname) {
  1141. if (isPersistent()) {
  1142. return true;
  1143. } else if (metaData != null && metaData.isFlatDomain("persistent")) {
  1144. final List<String> items = metaData.getFlatDomain("persistent");
  1145. return items.contains(classname);
  1146. } else {
  1147. return false;
  1148. }
  1149. }
  1150. /**
  1151. * Get the plugin Filename.
  1152. *
  1153. * @return Filename of plugin
  1154. */
  1155. public String getFilename() {
  1156. return filename;
  1157. }
  1158. /**
  1159. * Get the full plugin Filename (inc dirname)
  1160. *
  1161. * @return Filename of plugin
  1162. */
  1163. public String getFullFilename() {
  1164. return url.getPath();
  1165. }
  1166. /**
  1167. * Retrieves the path to this plugin relative to the main plugin directory,
  1168. * if appropriate.
  1169. *
  1170. * @return A relative path to the plugin if it is situated under the main
  1171. * plugin directory, or an absolute path otherwise.
  1172. */
  1173. public String getRelativeFilename() {
  1174. final String dir = new File(PluginManager.getPluginManager().getDirectory())
  1175. .getAbsolutePath() + File.separator;
  1176. final String file = new File(getFullFilename()).getAbsolutePath();
  1177. return file.startsWith(dir) ? getFullFilename().substring(dir.length()) : getFullFilename();
  1178. }
  1179. /**
  1180. * Get the plugin Author.
  1181. *
  1182. * @return Author of plugin
  1183. */
  1184. public String getAuthor() {
  1185. return getKeyValue("metadata", "author", "");
  1186. }
  1187. /**
  1188. * Get the plugin Description.
  1189. *
  1190. * @return Description of plugin
  1191. */
  1192. public String getDescription() {
  1193. return getKeyValue("metadata", "description", "");
  1194. }
  1195. /**
  1196. * Get the minimum dmdirc version required to run the plugin.
  1197. *
  1198. * @return minimum dmdirc version required to run the plugin.
  1199. */
  1200. public String getMinVersion() {
  1201. final String requiredVersion = getKeyValue("requires", "dmdirc", "");
  1202. if (!requiredVersion.isEmpty()) {
  1203. final String[] bits = requiredVersion.split("-");
  1204. return bits[0];
  1205. }
  1206. return "";
  1207. }
  1208. /**
  1209. * Get the (optional) maximum dmdirc version on which this plugin can run
  1210. *
  1211. * @return optional maximum dmdirc version on which this plugin can run
  1212. */
  1213. public String getMaxVersion() {
  1214. final String requiredVersion = getKeyValue("requires", "dmdirc", "");
  1215. if (!requiredVersion.isEmpty()) {
  1216. final String[] bits = requiredVersion.split("-");
  1217. if (bits.length > 1) {
  1218. return bits[1];
  1219. }
  1220. }
  1221. return "";
  1222. }
  1223. /**
  1224. * Get the name of the plugin. (Used to identify the plugin)
  1225. *
  1226. * @return Name of plugin
  1227. */
  1228. public String getName() {
  1229. return getKeyValue("metadata", "name", "");
  1230. }
  1231. /**
  1232. * Get the nice name of the plugin. (Displayed to users)
  1233. *
  1234. * @return Nice Name of plugin
  1235. */
  1236. public String getNiceName() {
  1237. return getKeyValue("metadata", "nicename", getName());
  1238. }
  1239. /**
  1240. * String Representation of this plugin
  1241. *
  1242. * @return String Representation of this plugin
  1243. */
  1244. @Override
  1245. public String toString() {
  1246. return getNiceName() + " - " + filename;
  1247. }
  1248. /**
  1249. * Does this plugin want all its classes loaded?
  1250. *
  1251. * @return true/false if loadall=true || loadall=yes
  1252. */
  1253. public boolean loadAll() {
  1254. final String loadAll = getKeyValue("metadata", "loadall", "no");
  1255. return loadAll.equalsIgnoreCase("true") || loadAll.equalsIgnoreCase("yes");
  1256. }
  1257. /**
  1258. * Get misc meta-information.
  1259. *
  1260. * @param metainfo The metainfo to return
  1261. * @deprecated Use {@link #getKeyValue(String, String, String) instead
  1262. * @return Misc Meta Info (or "" if not found);
  1263. */
  1264. @Deprecated
  1265. public String getMetaInfo(final String metainfo) {
  1266. return getMetaInfo(metainfo, "");
  1267. }
  1268. /**
  1269. * Get misc meta-information.
  1270. *
  1271. * @param metainfo The metainfo to return
  1272. * @param fallback Fallback value if requested value is not found
  1273. * @deprecated Use {@link #getKeyValue(String, String, String) instead
  1274. * @return Misc Meta Info (or fallback if not found);
  1275. */
  1276. @Deprecated
  1277. public String getMetaInfo(final String metainfo, final String fallback) {
  1278. return getKeyValue("misc", metainfo, fallback);
  1279. }
  1280. /**
  1281. * Get misc meta-information.
  1282. *
  1283. * @param metainfo The metainfos to look for in order. If the first item in
  1284. * the array is not found, the next will be looked for, and
  1285. * so on until either one is found, or none are found.
  1286. * @deprecated Use {@link #getKeyValue(String, String, String) instead
  1287. * @return Misc Meta Info (or "" if none are found);
  1288. */
  1289. @Deprecated
  1290. public String getMetaInfo(final String[] metainfo) {
  1291. return getMetaInfo(metainfo, "");
  1292. }
  1293. /**
  1294. * Get misc meta-information.
  1295. *
  1296. * @param metainfo The metainfos to look for in order. If the first item in
  1297. * the array is not found, the next will be looked for, and
  1298. * so on until either one is found, or none are found.
  1299. * @param fallback Fallback value if requested values are not found
  1300. * @deprecated Use {@link #getKeyValue(String, String, String) instead
  1301. * @return Misc Meta Info (or "" if none are found);
  1302. */
  1303. @Deprecated
  1304. public String getMetaInfo(final String[] metainfo, final String fallback) {
  1305. for (String meta : metainfo) {
  1306. final String result = getKeyValue("misc", meta, null);
  1307. if (result != null) {
  1308. return result;
  1309. }
  1310. }
  1311. return fallback;
  1312. }
  1313. /**
  1314. * Compares this object with the specified object for order.
  1315. * Returns a negative integer, zero, or a positive integer as per String.compareTo();
  1316. *
  1317. * @param o Object to compare to
  1318. * @return a negative integer, zero, or a positive integer.
  1319. */
  1320. @Override
  1321. public int compareTo(final PluginInfo o) {
  1322. return toString().compareTo(o.toString());
  1323. }
  1324. /**
  1325. * Update exports list.
  1326. */
  1327. private void updateExports() {
  1328. exports.clear();
  1329. // Get exports provided by this plugin
  1330. final List<String> exportsList = metaData.getFlatDomain("exports");
  1331. if (exportsList != null) {
  1332. for (String item : exportsList) {
  1333. final String[] bits = item.split(" ");
  1334. if (bits.length > 2) {
  1335. final String methodName = bits[0];
  1336. final String methodClass = bits[2];
  1337. final String serviceName = (bits.length > 4) ? bits[4] : bits[0];
  1338. // Add a provides for this
  1339. final Service service = PluginManager.getPluginManager().getService("export", serviceName, true);
  1340. service.addProvider(this);
  1341. provides.add(service);
  1342. // Add is as an export
  1343. exports.put(serviceName, new ExportInfo(methodName, methodClass, this));
  1344. }
  1345. }
  1346. }
  1347. }
  1348. /**
  1349. * Get an ExportedService object from this provider.
  1350. *
  1351. * @param name Service name
  1352. * @return ExportedService object. If no such service exists, the execute
  1353. * method of this ExportedService will always return null.
  1354. */
  1355. @Override
  1356. public ExportedService getExportedService(final String name) {
  1357. if (exports.containsKey(name)) {
  1358. return exports.get(name).getExportedService();
  1359. } else {
  1360. return new ExportedService(null, null);
  1361. }
  1362. }
  1363. /**
  1364. * Get the Plugin object for this plugin.
  1365. *
  1366. * @return Plugin object for the plugin
  1367. */
  1368. protected Plugin getPluginObject() {
  1369. return plugin;
  1370. }
  1371. }