File Source: PluginInfo.java

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








SofCheck Inspector Build Version : 2.17854
PluginInfo.java 2009-Jun-25 01:54:24
PluginInfo.class 2009-Sep-02 17:04:13
PluginInfo$1.class 2009-Sep-02 17:04:13