gt:             String webappDir = getProperty("webappDir");
context: package org.hotswap.agent.config;
  
  import java.io.File;
  import java.io.IOException;
  import java.net.MalformedURLException;
  import java.net.URL;
  import java.net.URLClassLoader;
  import java.util.ArrayList;
  import java.util.Arrays;
  import java.util.Enumeration;
  import java.util.List;
  import java.util.Properties;
  import java.util.StringTokenizer;
  
  import org.hotswap.agent.HotswapAgent;
  import org.hotswap.agent.annotation.Plugin;
  import org.hotswap.agent.logging.AgentLogger;
  import org.hotswap.agent.util.classloader.HotswapAgentClassLoaderExt;
  import org.hotswap.agent.util.classloader.URLClassLoaderHelper;
  
  /**
   * Plugin configuration.
   * <p/>
   * Single instance exists for each classloader.
   *
   * @author Jiri Bubnik
   */
  public class PluginConfiguration {
      private static AgentLogger LOGGER = AgentLogger.getLogger(PluginConfiguration.class);
  
      private static final String PLUGIN_CONFIGURATION = "hotswap-agent.properties";
  
      Properties properties = new Properties();
  
      // if the property is not defined in this classloader, look for parent classloader and it's configuration
      PluginConfiguration parent;
  
      // this configuration adheres to this classloader
      ClassLoader classLoader;
  
      // the hotswap-agent.properties file (or null if not defined for this classloader)
      URL configurationURL;
  
      // is property file defined directly in this classloader?
      boolean containsPropertyFileDirectly = false;
  
  
      public PluginConfiguration(ClassLoader classLoader) {
          this.classLoader = classLoader;
          configurationURL = classLoader == null ? ClassLoader.getSystemResource(PLUGIN_CONFIGURATION) : classLoader.getResource(PLUGIN_CONFIGURATION);
  
          try {
              if (configurationURL != null) {
                  containsPropertyFileDirectly = true;
                  properties.load(configurationURL.openStream());
                  init();
              }
          } catch (Exception e) {
              LOGGER.error("Error while loading 'hotswap-agent.properties' from base URL " + configurationURL, e);
          }
      }
  
      public PluginConfiguration(PluginConfiguration parent, ClassLoader classLoader) {
          this.parent = parent;
          this.classLoader = classLoader;
  
          // search for resources not known by parent classloader (defined in THIS classloader exclusively)
          // this is necessary in case of parent classloader precedence
          try {
              Enumeration<URL> urls = classLoader == null ? ClassLoader.getSystemResources(PLUGIN_CONFIGURATION) : classLoader.getResources(PLUGIN_CONFIGURATION);
              while (urls.hasMoreElements()) {
                  URL url = urls.nextElement();
  
                  boolean found = false;
  
                  if (parent != null) {
                      ClassLoader parentClassLoader = parent.getClassLoader();
                      Enumeration<URL> parentUrls = parentClassLoader == null ? ClassLoader.getSystemResources(PLUGIN_CONFIGURATION) : parentClassLoader.getResources(PLUGIN_CONFIGURATION);
                      while (parentUrls.hasMoreElements()) {
                          if (url.equals(parentUrls.nextElement()))
                              found = true;
                      }
                  }
  
                  if (!found) {
                      configurationURL = url;
                      break;
                  }
              }
          } catch (IOException e) {
              LOGGER.error("Error while loading 'hotswap-agent.properties' from URL " + configurationURL, e);
          }
  
          if (configurationURL == null && parent != null) {
              configurationURL = parent.configurationURL;
              LOGGER.info("Classloader does not contain 'hotswap-agent.properties', using parent file '{}'", parent.configurationURL);
          } else {
              LOGGER.info("Classloader contains 'hotswap-agent.properties' at location '{}'", configurationURL);
              containsPropertyFileDirectly = true;
          }
  
          try {
              if (configurationURL != null)
                  properties.load(configurationURL.openStream());
              init();
          } catch (Exception e) {
              LOGGER.error("Error while loading 'hotswap-agent.properties' from URL " + configurationURL, e);
          }
      }
  
      /**
       * Initialize the configuration.
       */
      protected void init() {
          LogConfigurationHelper.configureLog(properties);
  
          initPluginPackage();
  
          initExtraClassPath();
      }
  
      private void initPluginPackage() {
          // only for self property (not parent)
          if (properties.containsKey("pluginPackages")) {
              String pluginPackages = properties.getProperty("pluginPackages");
  
              for (String pluginPackage : pluginPackages.split(",")) {
                  PluginManager.getInstance().getPluginRegistry().scanPlugins(getClassLoader(), pluginPackage);
              }
          }
      }
  
      private void initExtraClassPath() {
          URL[] extraClassPath = getExtraClasspath();
          if (extraClassPath.length > 0) {
              if (classLoader instanceof URLClassLoader) {
                  URLClassLoaderHelper.prependClassPath((URLClassLoader) classLoader, extraClassPath);
              } else if (classLoader instanceof HotswapAgentClassLoaderExt) {
                  ((HotswapAgentClassLoaderExt) classLoader).setExtraClassPath(extraClassPath);
              } else {
                  LOGGER.info("Unable to set extraClasspath to {} on classLoader {}. " +
                          "Only URLClassLoader is supported.\n" +
                          "*** extraClasspath configuration property will not be handled on JVM level ***", Arrays.toString(extraClassPath), classLoader);
              }
          }
      }
  
      /**
       * Get configuration property value
       *
       * @param property property name
       * @return the property value or null if not defined
       */
      public String getProperty(String property) {
          if (properties.containsKey(property))
              return properties.getProperty(property);
          else if (parent != null)
              return parent.getProperty(property);
          else
              return null;
      }
  
      /**
       * Get configuration property value
       *
       * @param property property name
       * @param defaultValue value to return if property not defined
       * @return the property value or null if not defined
       */
      public String getProperty(String property, String defaultValue) {
          String value = getProperty(property);
          return value != null ? value : defaultValue;
      }
  
      /**
       * Convenience method to get property as a boolean value using Boolean.valueOf().
       *
       * @param property property name
       * @return the property value or null if not defined
       */
      public boolean getPropertyBoolean(String property) {
          if (properties.containsKey(property))
              return Boolean.valueOf(properties.getProperty(property));
          else if (parent != null)
              return parent.getPropertyBoolean(property);
          else
              return false;
      }
  
      /**
       * Get extraClasspath property as URL[].
       *
       * @return extraClasspath or empty array (never null)
       */
      public URL[] getExtraClasspath() {
          return convertToURL(getProperty("extraClasspath"));
      }
  
      /**
       * Converts watchResources property to URL array. Invalid URLs will be skipped and logged as error.
       */
      public URL[] getWatchResources() {
          return convertToURL(getProperty("watchResources"));
      }
  
      /**
       * Return configuration property webappDir as URL.
       */
      public URL getWebappDir() {
          try {
  