Gate.java
0001 /*
0002  *  Gate.java
0003  *
0004  *  Copyright (c) 1995-2012, The University of Sheffield. See the file
0005  *  COPYRIGHT.txt in the software or at http://gate.ac.uk/gate/COPYRIGHT.txt
0006  *
0007  *  This file is part of GATE (see http://gate.ac.uk/), and is free
0008  *  software, licenced under the GNU Library General Public License,
0009  *  Version 2, June 1991 (in the distribution as file licence.html,
0010  *  and also available at http://gate.ac.uk/gate/licence.html).
0011  *
0012  *  Hamish Cunningham, 31/07/98
0013  *
0014  *  $Id: Gate.java 18677 2015-05-11 10:15:41Z markagreenwood $
0015  */
0016 
0017 package gate;
0018 
0019 import gate.config.ConfigDataProcessor;
0020 import gate.creole.CreoleRegisterImpl;
0021 import gate.creole.ResourceData;
0022 import gate.creole.metadata.CreoleResource;
0023 import gate.event.CreoleListener;
0024 import gate.gui.creole.manager.PluginUpdateManager;
0025 import gate.util.Benchmark;
0026 import gate.util.Files;
0027 import gate.util.GateClassLoader;
0028 import gate.util.GateException;
0029 import gate.util.GateRuntimeException;
0030 import gate.util.LuckyException;
0031 import gate.util.OptionsMap;
0032 import gate.util.Strings;
0033 import gate.util.asm.AnnotationVisitor;
0034 import gate.util.asm.ClassReader;
0035 import gate.util.asm.ClassVisitor;
0036 import gate.util.asm.Opcodes;
0037 import gate.util.asm.Type;
0038 
0039 import java.io.File;
0040 import java.io.FileInputStream;
0041 import java.io.FileOutputStream;
0042 import java.io.IOException;
0043 import java.io.InputStream;
0044 import java.io.OutputStreamWriter;
0045 import java.net.HttpURLConnection;
0046 import java.net.InetAddress;
0047 import java.net.MalformedURLException;
0048 import java.net.URI;
0049 import java.net.URISyntaxException;
0050 import java.net.URL;
0051 import java.net.URLClassLoader;
0052 import java.net.URLConnection;
0053 import java.net.UnknownHostException;
0054 import java.util.ArrayList;
0055 import java.util.Collections;
0056 import java.util.EventListener;
0057 import java.util.HashMap;
0058 import java.util.HashSet;
0059 import java.util.Iterator;
0060 import java.util.LinkedHashMap;
0061 import java.util.List;
0062 import java.util.Map;
0063 import java.util.Set;
0064 import java.util.StringTokenizer;
0065 import java.util.jar.JarEntry;
0066 import java.util.jar.JarInputStream;
0067 
0068 import org.apache.log4j.Logger;
0069 import org.jdom.Attribute;
0070 import org.jdom.Element;
0071 import org.jdom.JDOMException;
0072 import org.jdom.input.SAXBuilder;
0073 import gate.util.asm.commons.EmptyVisitor;
0074 
0075 /**
0076  * The class is responsible for initialising the GATE libraries, and providing
0077  * access to singleton utility objects, such as the GATE class loader, CREOLE
0078  * register and so on.
0079  */
0080 public class Gate implements GateConstants {
0081 
0082   /** A logger to use instead of sending messages to Out or Err **/
0083   protected static final Logger log = Logger.getLogger(Gate.class);
0084 
0085   /**
0086    * The default StringBuffer size, it seems that we need longer string than the
0087    * StringBuffer class default because of the high number of buffer expansions
0088    */
0089   public static final int STRINGBUFFER_SIZE = 1024;
0090 
0091   /**
0092    * The default size to be used for Hashtable, HashMap and HashSet. The defualt
0093    * is 11 and it leads to big memory usage. Having a default load factor of
0094    * 0.75, table of size 4 can take 3 elements before being re-hashed - a values
0095    * that seems to be optimal for most of the cases.
0096    */
0097   public static final int HASH_STH_SIZE = 4;
0098 
0099   /** The list of builtin URLs to search for CREOLE resources. */
0100   private static String builtinCreoleDirectoryUrls[] {
0101   // "http://derwent.dcs.shef.ac.uk/gate.ac.uk/creole/"
0102 
0103     // this has been moved to initCreoleRegister and made relative to
0104     // the base URL returned by getUrl()
0105     // "http://gate.ac.uk/creole/"
0106     };
0107 
0108   /** The GATE URI used to interpret custom GATE tags */
0109   public static final String URI = "http://www.gate.ac.uk";
0110 
0111   /** Minimum version of JDK we support */
0112   protected static final String MIN_JDK_VERSION = "1.7";
0113 
0114   /**
0115    * Feature name that should be used to set if the benchmarking logging should
0116    * be enabled or disabled.
0117    */
0118   protected static final String ENABLE_BENCHMARKING_FEATURE_NAME =
0119     "gate.enable.benchmark";
0120 
0121   /** Is true if GATE is to be run in a sandbox **/
0122   private static boolean sandboxed = false;
0123 
0124   /**
0125    * Find out if GATE is to be run in a sandbox or not. If true then
0126    * GATE will not attempt to load any local configuration information during
0127    * Initialisation making it possible to use GATE from within unsigned
0128    * applets and web start applications.
0129    @return true if GATE is to be run in a sandbox, false otherwise
0130    */
0131   public static boolean isSandboxed() {
0132     return sandboxed;
0133   }
0134 
0135   /**
0136    * Method to tell GATE if it is being run inside a JVM sandbox. If true then
0137    * GATE will not attempt to load any local configuration information during
0138    * Initialisation making it possible to use GATE from within unsigned
0139    * applets and web start applications.
0140    @param sandboxed true if GATE is to be run in a sandbox, false otherwise
0141    */
0142   public static void runInSandbox(boolean sandboxed) {
0143     if (initFinished)
0144       throw new IllegalStateException("Sandbox status cannot be changed after GATE has been initialised!");
0145 
0146     Gate.sandboxed = sandboxed;
0147   }
0148 
0149   /** Get the minimum supported version of the JDK */
0150   public static String getMinJdkVersion() {
0151     return MIN_JDK_VERSION;
0152   }
0153 
0154   /**
0155    * Initialisation - must be called by all clients before using any other parts
0156    * of the library. Also initialises the CREOLE register and reads config data (<TT>gate.xml</TT>
0157    * files).
0158    *
0159    @see #initCreoleRegister
0160    */
0161   public static void init() throws GateException {
0162     // init local paths
0163     if (!sandboxedinitLocalPaths();
0164 
0165     // check if benchmarking should be enabled or disabled
0166     if(System.getProperty(ENABLE_BENCHMARKING_FEATURE_NAME!= null
0167       && System.getProperty(ENABLE_BENCHMARKING_FEATURE_NAME).equalsIgnoreCase(
0168         "true")) {
0169       Benchmark.setBenchmarkingEnabled(true);
0170     }
0171 
0172     // builtin creole dir
0173     if(builtinCreoleDir == null) {
0174       String builtinCreoleDirPropertyValue =
0175         System.getProperty(BUILTIN_CREOLE_DIR_PROPERTY_NAME);
0176       if(builtinCreoleDirPropertyValue == null) {
0177         // default to /gate/resources/creole in gate.jar
0178         builtinCreoleDir = Files.getGateResource("/creole/");
0179       }
0180       else {
0181         String builtinCreoleDirPath = builtinCreoleDirPropertyValue;
0182         // add a slash onto the end of the path if it doesn't have one already -
0183         // a creole directory URL should always end with a forward slash
0184         if(!builtinCreoleDirPath.endsWith("/")) {
0185           builtinCreoleDirPath += "/";
0186         }
0187         try {
0188           builtinCreoleDir = new URL(builtinCreoleDirPath);
0189         }
0190         catch(MalformedURLException mue) {
0191           // couldn't parse as a File either, so throw an exception
0192           throw new GateRuntimeException(BUILTIN_CREOLE_DIR_PROPERTY_NAME
0193             " value \"" + builtinCreoleDirPropertyValue + "\" could"
0194             " not be parsed as either a URL or a file path.");
0195         }
0196         log.info("Using " + builtinCreoleDir + " as built-in CREOLE"
0197           " directory URL");
0198       }
0199     }
0200 
0201     // initialise the symbols generator
0202     lastSym = 0;
0203 
0204     // create class loader and creole register if they're null
0205     if(classLoader == null)
0206       classLoader = new GateClassLoader("Top-Level GATE ClassLoader", Gate.class.getClassLoader());
0207     if(creoleRegister == nullcreoleRegister = new CreoleRegisterImpl();
0208     if(knownPlugins == nullknownPlugins = new ArrayList<URL>();
0209     if(autoloadPlugins == nullautoloadPlugins = new ArrayList<URL>();
0210     if(pluginData == nullpluginData = new HashMap<URL, DirectoryInfo>();
0211     // init the creole register
0212     initCreoleRegister();
0213     // init the data store register
0214     initDataStoreRegister();
0215 
0216     // read gate.xml files; this must come before creole register
0217     // initialisation in order for the CREOLE-DIR elements to have and effect
0218     if (!sandboxedinitConfigData();
0219 
0220     if (!sandboxedinitCreoleRepositories();
0221     // the creoleRegister acts as a proxy for datastore related events
0222     dataStoreRegister.addCreoleListener(creoleRegister);
0223 
0224     // some of the events are actually fired by the {@link gate.Factory}
0225     Factory.addCreoleListener(creoleRegister);
0226 
0227     // check we have a useable JDK
0228     if(System.getProperty("java.version").compareTo(MIN_JDK_VERSION0) { throw new GateException(
0229       "GATE requires JDK " + MIN_JDK_VERSION + " or newer")}
0230 
0231     initFinished = true;
0232   // init()
0233 
0234   /** Have we successfully run {@link #init()} before? */
0235   public static boolean isInitialised() { return initFinished; }
0236 
0237   /** Records initialisation status. */
0238   protected static boolean initFinished = false;
0239 
0240   /**
0241    * Initialises the paths to local files of interest like the GATE home, the
0242    * installed plugins home and site and user configuration files.
0243    */
0244   protected static void initLocalPaths() {
0245     // GATE Home
0246     if(gateHome == null) {
0247       String gateHomeStr = System.getProperty(GATE_HOME_PROPERTY_NAME);
0248       if(gateHomeStr != null && gateHomeStr.length() 0) {
0249         gateHome = new File(gateHomeStr);
0250       }
0251       // if failed, try to guess
0252       if(gateHome == null || !gateHome.exists()) {
0253         log.warn("GATE home system property (\"" + GATE_HOME_PROPERTY_NAME
0254           "\") not set.\nAttempting to guess...");
0255         URL gateURL =
0256           Thread.currentThread().getContextClassLoader().getResource(
0257             "gate/Gate.class");
0258         try {
0259           if(gateURL.getProtocol().equals("jar")) {
0260             // running from gate.jar
0261             String gateURLStr = gateURL.getFile();
0262             File gateJarFile =
0263               new File(
0264                 new URI(gateURLStr.substring(0, gateURLStr.indexOf('!'))));
0265             gateHome = gateJarFile.getParentFile().getParentFile();
0266           }
0267           else if(gateURL.getProtocol().equals("file")) {
0268             // running from classes directory
0269             File gateClassFile = Files.fileFromURL(gateURL);
0270             gateHome =
0271               gateClassFile.getParentFile().getParentFile().getParentFile();
0272           }
0273           log.warn("Using \"" + gateHome.getCanonicalPath()
0274             "\" as GATE Home.\nIf this is not correct please set it manually"
0275             " using the -D" + GATE_HOME_PROPERTY_NAME
0276             " option in your start-up script");
0277         }
0278         catch(Throwable thr) {
0279           throw new GateRuntimeException(
0280             "Cannot guess GATE Home. Pease set it manually!", thr);
0281         }
0282       }
0283     }
0284     log.info("Using " + gateHome.toString() " as GATE home");
0285 
0286     // Plugins home
0287     if(pluginsHome == null) {
0288       String pluginsHomeStr = System.getProperty(PLUGINS_HOME_PROPERTY_NAME);
0289       if(pluginsHomeStr != null && pluginsHomeStr.length() 0) {
0290         File homeFile = new File(pluginsHomeStr);
0291         if(homeFile.exists() && homeFile.isDirectory()) {
0292           pluginsHome = homeFile;
0293         }
0294       }
0295       // if not set, use the GATE Home as a base directory
0296       if(pluginsHome == null) {
0297         File homeFile = new File(gateHome, PLUGINS);
0298         if(homeFile.exists() && homeFile.isDirectory()) {
0299           pluginsHome = homeFile;
0300         }
0301       }
0302       // if still not set, throw exception
0303       if(pluginsHome == null) { throw new GateRuntimeException(
0304         "Could not infer installed plug-ins home!\n"
0305           "Please set it manually using the -D" + PLUGINS_HOME_PROPERTY_NAME
0306           " option in your start-up script.")}
0307     }
0308     log.info("Using " + pluginsHome.toString()
0309       " as installed plug-ins directory.");
0310 
0311     // site config
0312     if(siteConfigFile == null) {
0313       String siteConfigStr = System.getProperty(SITE_CONFIG_PROPERTY_NAME);
0314       if(siteConfigStr != null && siteConfigStr.length() 0) {
0315         File configFile = new File(siteConfigStr);
0316         if(configFile.exists()) siteConfigFile = configFile;
0317       }
0318       // if not set, use GATE home as base directory
0319       if(siteConfigFile == null) {
0320         File configFile = new File(gateHome, GATE_DOT_XML);
0321         if(configFile.exists()) siteConfigFile = configFile;
0322       }
0323       // if still not set, throw exception
0324       if(siteConfigFile == null) { throw new GateRuntimeException(
0325         "Could not locate the site configuration file!\n"
0326           "Please create it at "
0327           new File(gateHome, GATE_DOT_XML).toString()
0328           " or point to an existing one using the -D"
0329           + SITE_CONFIG_PROPERTY_NAME + " option in your start-up script!")}
0330     }
0331     log.info("Using " + siteConfigFile.toString()
0332       " as site configuration file.");
0333 
0334     // user config
0335     if(userConfigFile == null) {
0336       String userConfigStr = System.getProperty(USER_CONFIG_PROPERTY_NAME);
0337       if(userConfigStr != null && userConfigStr.length() 0) {
0338         userConfigFile = new File(userConfigStr);
0339       else {
0340         userConfigFile = new File(getDefaultUserConfigFileName());
0341       }
0342     }
0343     log.info("Using " + userConfigFile + " as user configuration file");
0344 
0345     // user session
0346     if(userSessionFile == null) {
0347       String userSessionStr =
0348         System.getProperty(GATE_USER_SESSION_PROPERTY_NAME);
0349       if(userSessionStr != null && userSessionStr.length() 0) {
0350         userSessionFile = new File(userSessionStr);
0351       }
0352       else {
0353         userSessionFile = new File(getDefaultUserSessionFileName());
0354       }
0355     }// if(userSessionFile == null)
0356     log.info("Using " + userSessionFile + " as user session file");
0357   }
0358 
0359   /**
0360    * Loads the CREOLE repositories (aka plugins) that the user has selected for
0361    * automatic loading. Loads the information about known plugins in memory.
0362    */
0363   protected static void initCreoleRepositories() {
0364     // the logic is:
0365     // get the list of know plugins from gate.xml
0366     // add all the installed plugins
0367     // get the list of loadable plugins
0368     // load loadable plugins
0369 
0370     // process the known plugins list
0371     String knownPluginsPath =
0372       (String)getUserConfig().get(KNOWN_PLUGIN_PATH_KEY);
0373     if(knownPluginsPath != null && knownPluginsPath.length() 0) {
0374       StringTokenizer strTok =
0375         new StringTokenizer(knownPluginsPath, ";"false);
0376       while(strTok.hasMoreTokens()) {
0377         String aKnownPluginPath = strTok.nextToken();
0378         try {
0379           URL aPluginURL = new URL(aKnownPluginPath);
0380           addKnownPlugin(aPluginURL);
0381         }
0382         catch(MalformedURLException mue) {
0383           log.error("Plugin error: " + aKnownPluginPath + " is an invalid URL!");
0384         }
0385       }
0386     }
0387     // add all the installed plugins
0388     // pluginsHome is now set by initLocalPaths
0389     // File pluginsHome = new File(System.getProperty(GATE_HOME_PROPERTY_NAME),
0390     // "plugins");
0391     File[] dirs = pluginsHome.listFiles();
0392     for(int i = 0; i < dirs.length; i++) {
0393       File creoleFile = new File(dirs[i]"creole.xml");
0394       if(creoleFile.exists()) {
0395         try {
0396           URL pluginURL = dirs[i].toURI().toURL();
0397           addKnownPlugin(pluginURL);
0398         }
0399         catch(MalformedURLException mue) {
0400           // this should never happen
0401           throw new GateRuntimeException(mue);
0402         }
0403       }
0404     }
0405     
0406     // register plugins installed in the user plugin directory
0407     File userPluginsHome = PluginUpdateManager.getUserPluginsHome();
0408     if (userPluginsHome != null && userPluginsHome.isDirectory()) {
0409       for (File dir : userPluginsHome.listFiles()) {
0410         File creoleFile = new File(dir, "creole.xml");
0411         if(creoleFile.exists()) {
0412           try {
0413             URL pluginURL = dir.toURI().toURL();
0414             addKnownPlugin(pluginURL);
0415           }
0416           catch(MalformedURLException mue) {
0417             // this should never happen
0418             throw new GateRuntimeException(mue);
0419           }
0420         }
0421       }
0422     }
0423 
0424     // process the autoload plugins
0425     String pluginPath = getUserConfig().getString(AUTOLOAD_PLUGIN_PATH_KEY);
0426     // can be overridden by system property
0427     String prop = System.getProperty(AUTOLOAD_PLUGIN_PATH_PROPERTY_NAME);
0428     if(prop != null && prop.length() 0pluginPath = prop;
0429 
0430     if(pluginPath == null || pluginPath.length() == 0) {
0431       // no plugin to load stop here
0432       return;
0433     }
0434 
0435     // load all loadable plugins
0436     StringTokenizer strTok = new StringTokenizer(pluginPath, ";"false);
0437     while(strTok.hasMoreTokens()) {
0438       String aDir = strTok.nextToken();
0439       try {
0440         URL aPluginURL = new URL(aDir);
0441         addAutoloadPlugin(aPluginURL);
0442       }
0443       catch(MalformedURLException mue) {
0444         log.error("Cannot load " + aDir + " CREOLE repository.",mue);
0445       }
0446       try {
0447         Iterator<URL> loadPluginsIter = getAutoloadPlugins().iterator();
0448         while(loadPluginsIter.hasNext()) {
0449           getCreoleRegister().registerDirectories(loadPluginsIter.next());
0450         }
0451       }
0452       catch(GateException ge) {
0453         log.error("Cannot load " + aDir + " CREOLE repository.",ge);
0454       }
0455     }
0456   }
0457 
0458   /** Initialise the CREOLE register. */
0459   public static void initCreoleRegister() throws GateException {
0460 
0461     // register the builtin CREOLE directories
0462     for(int i = 0; i < builtinCreoleDirectoryUrls.length; i++)
0463       try {
0464         creoleRegister.registerDirectories(new URL(builtinCreoleDirectoryUrls[i]));
0465       }
0466       catch(MalformedURLException e) {
0467         throw new GateException(e);
0468       }
0469 
0470     /*
0471      * We'll have to think about this. Right now it points to the creole inside
0472      * the jar/classpath so it's the same as registerBuiltins
0473      */
0474     // // add the GATE base URL creole directory
0475     // creoleRegister.addDirectory(Gate.getUrl("creole/"));
0476     // creoleRegister.registerDirectories();
0477     // register the resources that are actually in gate.jar
0478     creoleRegister.registerBuiltins();
0479   // initCreoleRegister
0480 
0481   /** Initialise the DataStore register. */
0482   public static void initDataStoreRegister() {
0483     dataStoreRegister = new DataStoreRegister();
0484   // initDataStoreRegister()
0485 
0486   /**
0487    * Reads config data (<TT>gate.xml</TT> files). There are three sorts of
0488    * these files:
0489    <UL>
0490    <LI> The builtin file from GATE's resources - this is read first.
0491    <LI> A site-wide init file given as a command-line argument or as a
0492    <TT>gate.config</TT> property - this is read second.
0493    <LI> The user's file from their home directory - this is read last.
0494    </UL>
0495    * Settings from files read after some settings have already been made will
0496    * simply overwrite the previous settings.
0497    */
0498   public static void initConfigData() throws GateException {
0499     ConfigDataProcessor configProcessor = new ConfigDataProcessor();
0500     // parse the site configuration file
0501     URL configURL;
0502     try {
0503       configURL = siteConfigFile.toURI().toURL();
0504     }
0505     catch(MalformedURLException mue) {
0506       // this should never happen
0507       throw new GateRuntimeException(mue);
0508     }
0509     try {
0510       InputStream configStream = new FileInputStream(siteConfigFile);
0511       configProcessor.parseConfigFile(configStream, configURL);
0512     }
0513     catch(IOException e) {
0514       throw new GateException("Couldn't open site configuration file: "
0515         + configURL + " " + e);
0516     }
0517 
0518     // parse the user configuration data if present
0519     if(userConfigFile != null && userConfigFile.exists()) {
0520       try {
0521         configURL = userConfigFile.toURI().toURL();
0522       }
0523       catch(MalformedURLException mue) {
0524         // this should never happen
0525         throw new GateRuntimeException(mue);
0526       }
0527       try {
0528         InputStream configStream = new FileInputStream(userConfigFile);
0529         configProcessor.parseConfigFile(configStream, configURL);
0530       }
0531       catch(IOException e) {
0532         throw new GateException("Couldn't open user configuration file: "
0533           + configURL + " " + e);
0534       }
0535     }
0536 
0537     // remember the init-time config options
0538     originalUserConfig.putAll(userConfig);
0539   // initConfigData()
0540 
0541   /**
0542    * Get a URL that points to either an HTTP server or a file system that
0543    * contains GATE files (such as test cases). The following locations are tried
0544    * in sequence:
0545    <UL>
0546    <LI> <TT>http://derwent.dcs.shef.ac.uk/gate.ac.uk/</TT>, a
0547    * Sheffield-internal development server (the gate.ac.uk affix is a copy of
0548    * the file system present on GATE's main public server - see next item);
0549    <LI> <TT>http://gate.ac.uk/</TT>, GATE's main public server;
0550    <LI> <TT>http://localhost/gate.ac.uk/</TT>, a Web server running on the
0551    * local machine;
0552    <LI> the local file system where the binaries for the current invocation of
0553    * GATE are stored.
0554    </UL>
0555    * In each case we assume that a Web server will be running on port 80, and
0556    * that if we can open a socket to that port then the server is running. (This
0557    * is a bit of a strong assumption, but this URL is used largely by the test
0558    * suite, so we're not betting anything too critical on it.)
0559    <P>
0560    * Note that the value returned will only be calculated when the existing
0561    * value recorded by this class is null (which will be the case when neither
0562    * setUrlBase nor getUrlBase have been called, or if setUrlBase(null) has been
0563    * called).
0564    */
0565   public static URL getUrl() throws GateException {
0566     if(urlBase != nullreturn urlBase;
0567 
0568     try {
0569 
0570       // if we're assuming a net connection, try network servers
0571       if(isNetConnected()) {
0572         if(
0573         // tryNetServer("gate-internal.dcs.shef.ac.uk", 80, "/") ||
0574         // tryNetServer("derwent.dcs.shef.ac.uk", 80, "/gate.ac.uk/") ||
0575         tryNetServer("gate.ac.uk"80"/")) {
0576           log.debug("getUrl() returned " + urlBase);
0577           return urlBase;
0578         }
0579       // if isNetConnected() ...
0580 
0581       // no network servers; try for a local host web server.
0582       // we use InetAddress to get host name instead of using "localhost" coz
0583       // badly configured Windoze IP sometimes doesn't resolve the latter
0584       if(isLocalWebServer()
0585         && tryNetServer(InetAddress.getLocalHost().getHostName()80,
0586           "/gate.ac.uk/")) {
0587         log.debug("getUrlBase() returned " + urlBase);
0588         return urlBase;
0589       }
0590 
0591       // try the local file system
0592       tryFileSystem();
0593 
0594     }
0595     catch(MalformedURLException e) {
0596       throw new GateException("Bad URL, getUrlBase(): " + urlBase + ": " + e);
0597     }
0598     catch(UnknownHostException e) {
0599       throw new GateException("No host, getUrlBase(): " + urlBase + ": " + e);
0600     }
0601 
0602     // return value will be based on the file system, or null
0603     log.debug("getUrlBase() returned " + urlBase);
0604     return urlBase;
0605   // getUrl()
0606 
0607   /**
0608    * Get a URL that points to either an HTTP server or a file system that
0609    * contains GATE files (such as test cases). Calls <TT>getUrl()</TT> then
0610    * adds the <TT>path</TT> parameter to the result.
0611    *
0612    @param path
0613    *          a path to add to the base URL.
0614    @see #getUrl()
0615    */
0616   public static URL getUrl(String paththrows GateException {
0617     getUrl();
0618     if(urlBase == nullreturn null;
0619 
0620     URL newUrl = null;
0621     try {
0622       newUrl = new URL(urlBase, path);
0623     }
0624     catch(MalformedURLException e) {
0625       throw new GateException("Bad URL, getUrl( " + path + "): " + e);
0626     }
0627 
0628     log.debug("getUrl(" + path + ") returned " + newUrl);
0629     return newUrl;
0630   // getUrl(path)
0631 
0632   /**
0633    * Flag controlling whether we should try to access the net, e.g. when setting
0634    * up a base URL.
0635    */
0636   private static boolean netConnected = false;
0637 
0638   private static int lastSym;
0639 
0640   /**
0641    * A list of names of classes that implement {@link gate.creole.ir.IREngine}
0642    * that will be used as information retrieval engines.
0643    */
0644   private static Set<String> registeredIREngines = new HashSet<String>();
0645 
0646   /**
0647    * Registers a new IR engine. The class named should implement
0648    * {@link gate.creole.ir.IREngine} and be accessible via the GATE class
0649    * loader.
0650    *
0651    @param className
0652    *          the fully qualified name of the class to be registered
0653    @throws GateException
0654    *           if the class does not implement the
0655    *           {@link gate.creole.ir.IREngine} interface.
0656    @throws ClassNotFoundException
0657    *           if the named class cannot be found.
0658    */
0659   public static void registerIREngine(String classNamethrows GateException,
0660     ClassNotFoundException {
0661     Class<?> aClass = Class.forName(className, true, Gate.getClassLoader());
0662     if(gate.creole.ir.IREngine.class.isAssignableFrom(aClass)) {
0663       registeredIREngines.add(className);
0664     }
0665     else {
0666       throw new GateException(className + " does not implement the "
0667         + gate.creole.ir.IREngine.class.getName() " interface!");
0668     }
0669   }
0670 
0671   /**
0672    * Unregisters a previously registered IR engine.
0673    *
0674    @param className
0675    *          the name of the class to be removed from the list of registered IR
0676    *          engines.
0677    @return true if the class was found and removed.
0678    */
0679   public static boolean unregisterIREngine(String className) {
0680     return registeredIREngines.remove(className);
0681   }
0682 
0683   /**
0684    * Gets the set of registered IR engines.
0685    *
0686    @return an unmodifiable {@link java.util.Set} value.
0687    */
0688   public static Set<String> getRegisteredIREngines() {
0689     return Collections.unmodifiableSet(registeredIREngines);
0690   }
0691 
0692   /**
0693    * Gets the GATE home location.
0694    *
0695    @return a File value.
0696    */
0697   public static File getGateHome() {
0698     return gateHome;
0699   }
0700 
0701   /** Should we assume we're connected to the net? */
0702   public static boolean isNetConnected() {
0703     return netConnected;
0704   }
0705 
0706   /**
0707    * Tell GATE whether to assume we're connected to the net. Has to be called
0708    <B>before</B> {@link #init()}.
0709    */
0710   public static void setNetConnected(boolean b) {
0711     netConnected = b;
0712   }
0713 
0714   /**
0715    * Flag controlling whether we should try to access a web server on localhost,
0716    * e.g. when setting up a base URL. Has to be called <B>before</B>
0717    * {@link #init()}.
0718    */
0719   private static boolean localWebServer = false;
0720 
0721   /** Should we assume there's a local web server? */
0722   public static boolean isLocalWebServer() {
0723     return localWebServer;
0724   }
0725 
0726   /** Tell GATE whether to assume there's a local web server. */
0727   public static void setLocalWebServer(boolean b) {
0728     localWebServer = b;
0729   }
0730 
0731   /**
0732    * Try to contact a network server. When sucessfull sets urlBase to an HTTP
0733    * URL for the server.
0734    *
0735    @param hostName
0736    *          the name of the host to try and connect to
0737    @param serverPort
0738    *          the port to try and connect to
0739    @param path
0740    *          a path to append to the URL when we make a successfull connection.
0741    *          E.g. for host xyz, port 80, path /thing, the resultant URL would
0742    *          be <TT>http://xyz:80/thing</TT>.
0743    */
0744   public static boolean tryNetServer(String hostName, int serverPort,
0745     String paththrows MalformedURLException {
0746 
0747     log.debug("tryNetServer(hostName=" + hostName + ", serverPort="
0748         + serverPort + ", path=" + path + ")");
0749 
0750     // is the host listening at the port?
0751     try {
0752       URL url = new URL("http://" + hostName + ":" + serverPort + "/");
0753       URLConnection uConn = url.openConnection();
0754       HttpURLConnection huConn = null;
0755       if(uConn instanceof HttpURLConnectionhuConn = (HttpURLConnection)uConn;
0756       if(huConn.getResponseCode() == -1return false;
0757     }
0758     catch(IOException e) {
0759       return false;
0760     }
0761 
0762     // if(socket != null) {
0763     urlBase = new URL("http", hostName, serverPort, path);
0764     return true;
0765     // }
0766 
0767     // return false;
0768   // tryNetServer()
0769 
0770   /** Try to find GATE files in the local file system */
0771   protected static boolean tryFileSystem() throws MalformedURLException {
0772     String urlBaseName = locateGateFiles();
0773     log.debug("tryFileSystem: " + urlBaseName);
0774 
0775     urlBase = new URL(urlBaseName + "gate/resources/gate.ac.uk/");
0776     return urlBase == null;
0777   // tryFileSystem()
0778 
0779   /**
0780    * Find the location of the GATE binaries (and resources) in the local file
0781    * system.
0782    */
0783   public static String locateGateFiles() {
0784     String aGateResourceName = "gate/resources/creole/creole.xml";
0785     URL resourcesUrl = Gate.getClassLoader().getResource(aGateResourceName);
0786 
0787     StringBuffer basePath = new StringBuffer(resourcesUrl.toExternalForm());
0788     String urlBaseName =
0789       basePath.substring(0, basePath.length() - aGateResourceName.length());
0790 
0791     return urlBaseName;
0792   // locateGateFiles
0793 
0794   /**
0795    * Checks whether a particular class is a Gate defined type
0796    */
0797   public static boolean isGateType(String classname) {
0798     boolean res = getCreoleRegister().containsKey(classname);
0799     if(!res) {
0800       try {
0801         Class<?> aClass = Class.forName(classname, true, Gate.getClassLoader());
0802         res =
0803           Resource.class.isAssignableFrom(aClass);
0804       }
0805       catch(ClassNotFoundException cnfe) {
0806         return false;
0807       }
0808     }
0809     return res;
0810   }
0811 
0812   /** Returns the value for the HIDDEN attribute of a feature map */
0813   static public boolean getHiddenAttribute(FeatureMap fm) {
0814     if(fm == nullreturn false;
0815     Object value = fm.get(HIDDEN_FEATURE_KEY);
0816     return value != null && value instanceof String
0817       && ((String)value).equals("true");
0818   }
0819 
0820   /** Sets the value for the HIDDEN attribute of a feature map */
0821   static public void setHiddenAttribute(FeatureMap fm, boolean hidden) {
0822     if(hidden) {
0823       fm.put(HIDDEN_FEATURE_KEY, "true");
0824     }
0825     else {
0826       fm.remove(HIDDEN_FEATURE_KEY);
0827     }
0828   }
0829 
0830   /**
0831    * Registers a {@link gate.event.CreoleListener} with the Gate system
0832    */
0833   public static synchronized void addCreoleListener(CreoleListener l) {
0834     creoleRegister.addCreoleListener(l);
0835   // addCreoleListener
0836 
0837   /** Set the URL base for GATE files, e.g. <TT>http://gate.ac.uk/</TT>. */
0838   public static void setUrlBase(URL urlBase) {
0839     Gate.urlBase = urlBase;
0840   }
0841 
0842   /** The URL base for GATE files, e.g. <TT>http://gate.ac.uk/</TT>. */
0843   private static URL urlBase = null;
0844 
0845   /**
0846    * Class loader used e.g. for loading CREOLE modules, of compiling JAPE rule
0847    * RHSs.
0848    */
0849   private static GateClassLoader classLoader = null;
0850 
0851   /** Get the GATE class loader. */
0852   public static GateClassLoader getClassLoader() {
0853     return classLoader;
0854   }
0855 
0856   /** The CREOLE register. */
0857   private static CreoleRegister creoleRegister = null;
0858 
0859   /** Get the CREOLE register. */
0860   public static CreoleRegister getCreoleRegister() {
0861     return creoleRegister;
0862   }
0863 
0864   /** The DataStore register */
0865   private static DataStoreRegister dataStoreRegister = null;
0866 
0867   /**
0868    * The current executable under execution.
0869    */
0870   private static gate.Executable currentExecutable;
0871 
0872   /** Get the DataStore register. */
0873   public static DataStoreRegister getDataStoreRegister() {
0874     return dataStoreRegister;
0875   // getDataStoreRegister
0876 
0877   /**
0878    * Sets the {@link Executable} currently under execution. At a given time
0879    * there can be only one executable set. After the executable has finished its
0880    * execution this value should be set back to null. An attempt to set the
0881    * executable while this value is not null will result in the method call
0882    * waiting until the old executable is set to null.
0883    */
0884   public synchronized static void setExecutable(gate.Executable executable) {
0885     if(executable == null)
0886       currentExecutable = executable;
0887     else {
0888       while(getExecutable() != null) {
0889         try {
0890           Thread.sleep(200);
0891         }
0892         catch(InterruptedException ie) {
0893           throw new LuckyException(ie.toString());
0894         }
0895       }
0896       currentExecutable = executable;
0897     }
0898   // setExecutable
0899 
0900   /**
0901    * Returns the curently set executable.
0902    *
0903    @see #setExecutable(gate.Executable)
0904    */
0905   public synchronized static gate.Executable getExecutable() {
0906     return currentExecutable;
0907   // getExecutable
0908 
0909   /**
0910    * Returns a new unique string
0911    */
0912   public synchronized static String genSym() {
0913     StringBuffer buff =
0914       new StringBuffer(Integer.toHexString(lastSym++).toUpperCase());
0915     for(int i = buff.length(); i <= 4; i++)
0916       buff.insert(0'0');
0917     return buff.toString();
0918   // genSym
0919 
0920   /** GATE development environment configuration data (stored in gate.xml). */
0921   private static OptionsMap userConfig = new OptionsMap();
0922 
0923   /**
0924    * This map stores the init-time config data in case we need it later. GATE
0925    * development environment configuration data (stored in gate.xml).
0926    */
0927   private static OptionsMap originalUserConfig = new OptionsMap();
0928 
0929   /** Name of the XML element for GATE development environment config data. */
0930   private static String userConfigElement = "GATECONFIG";
0931 
0932   /**
0933    * Gate the name of the XML element for GATE development environment config
0934    * data.
0935    */
0936   public static String getUserConfigElement() {
0937     return userConfigElement;
0938   }
0939 
0940   /**
0941    * Get the site config file (generally set during command-line processing or
0942    * as a <TT>gate.config</TT> property). If the config is null, this method
0943    * checks the <TT>gate.config</TT> property and uses it if non-null.
0944    */
0945   public static File getSiteConfigFile() {
0946     if(siteConfigFile == null) {
0947       String gateConfigProperty = System.getProperty(GATE_CONFIG_PROPERTY);
0948       if(gateConfigProperty != null)
0949         siteConfigFile = new File(gateConfigProperty);
0950     }
0951     return siteConfigFile;
0952   // getSiteConfigFile
0953 
0954   /** Set the site config file (e.g. during command-line processing). */
0955   public static void setSiteConfigFile(File siteConfigFile) {
0956     Gate.siteConfigFile = siteConfigFile;
0957   // setSiteConfigFile
0958 
0959   /** Shorthand for local newline */
0960   private static String nl = Strings.getNl();
0961 
0962   /** An empty config data file. */
0963   private static String emptyConfigFile =
0964     "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" + nl + "<!-- " + GATE_DOT_XML
0965       ": GATE configuration data -->" + nl + "<GATE>" + nl + "" + nl
0966       "<!-- NOTE: the next element may be overwritten by the GUI!!! -->" + nl
0967       "<" + userConfigElement + "/>" + nl + "" + nl + "</GATE>" + nl;
0968 
0969   /**
0970    * Get an empty config file. <B>NOTE:</B> this method is intended only for
0971    * use by the test suite.
0972    */
0973   public static String getEmptyConfigFile() {
0974     return emptyConfigFile;
0975   }
0976 
0977   /**
0978    * Get the GATE development environment configuration data (initialised from
0979    <TT>gate.xml</TT>).
0980    */
0981   public static OptionsMap getUserConfig() {
0982     return userConfig;
0983   }
0984 
0985   /**
0986    * Get the original, initialisation-time, GATE development environment
0987    * configuration data (initialised from <TT>gate.xml</TT>).
0988    */
0989   public static OptionsMap getOriginalUserConfig() {
0990     return originalUserConfig;
0991   // getOriginalUserConfig
0992 
0993   /**
0994    * Update the GATE development environment configuration data in the user's
0995    <TT>gate.xml</TT> file (create one if it doesn't exist).
0996    */
0997   public static void writeUserConfig() throws GateException {
0998 
0999     //if we are running in a sandbox then don't try and write anything
1000     if (sandboxedreturn;
1001 
1002     String pluginsHomeStr;
1003     try {
1004       pluginsHomeStr = pluginsHome.getCanonicalPath();
1005     }
1006     catch(IOException ioe) {
1007       throw new GateRuntimeException(
1008         "Problem while locating the plug-ins home!", ioe);
1009     }
1010         
1011     String userPluginHomeStr;
1012     try {
1013       File userPluginHome = PluginUpdateManager.getUserPluginsHome();
1014       userPluginHomeStr = (userPluginHome != null ? userPluginHome.getCanonicalPath() null);
1015     }
1016     catch (IOException ioe) {
1017       throw new GateRuntimeException("Unable to access user plugin directory!", ioe);
1018     }
1019     
1020     // update the values for knownPluginPath
1021     String knownPluginPath = "";
1022     Iterator<URL> pluginIter = getKnownPlugins().iterator();
1023     while(pluginIter.hasNext()) {
1024       URL aPluginURL = pluginIter.next();
1025       // do not save installed plug-ins - they get loaded automatically
1026       if(aPluginURL.getProtocol().equals("file")) {
1027         File pluginDirectory = Files.fileFromURL(aPluginURL);
1028         try {
1029           if(pluginDirectory.getCanonicalPath().startsWith(pluginsHomeStr))
1030             continue;
1031           
1032           if (userPluginHomeStr != null && pluginDirectory.getCanonicalPath().startsWith(userPluginHomeStr))
1033             continue;
1034         }
1035         catch(IOException ioe) {
1036           throw new GateRuntimeException("Problem while locating the plug-in"
1037             + aPluginURL.toString(), ioe);
1038         }
1039       }
1040       if(knownPluginPath.length() 0knownPluginPath += ";";
1041       knownPluginPath += aPluginURL.toExternalForm();
1042     }
1043     getUserConfig().put(KNOWN_PLUGIN_PATH_KEY, knownPluginPath);
1044 
1045     // update the autoload plugin list
1046     String loadPluginPath = "";
1047     pluginIter = getAutoloadPlugins().iterator();
1048     while(pluginIter.hasNext()) {
1049       URL aPluginURL = pluginIter.next();
1050       if(loadPluginPath.length() 0loadPluginPath += ";";
1051       loadPluginPath += aPluginURL.toExternalForm();
1052     }
1053     getUserConfig().put(AUTOLOAD_PLUGIN_PATH_KEY, loadPluginPath);
1054 
1055     // the user's config file
1056     // String configFileName = getUserConfigFileName();
1057     // File configFile = new File(configFileName);
1058     File configFile = getUserConfigFile();
1059 
1060     // create if not there, then update
1061     try {
1062       // if the file doesn't exist, create one with an empty GATECONFIG
1063       if(!configFile.exists()) {
1064         FileOutputStream fos = new FileOutputStream(configFile);
1065         OutputStreamWriter writer = new OutputStreamWriter(fos, "UTF-8");
1066         writer.write(emptyConfigFile);
1067         writer.close();
1068       }
1069 
1070       // update the config element of the file
1071       Files.updateXmlElement(configFile, userConfigElement, userConfig.getStringMap());
1072 
1073     }
1074     catch(IOException e) {
1075       throw new GateException("problem writing user " + GATE_DOT_XML + ": "
1076         + nl + e.toString());
1077     }
1078   // writeUserConfig
1079 
1080   /**
1081    * Get the default path to the user's config file, which is used unless an
1082    * alternative name has been specified via system properties or
1083    * {@link #setUserConfigFile}.
1084    *
1085    @return the default user config file path.
1086    */
1087   public static String getDefaultUserConfigFileName() {
1088     String filePrefix = "";
1089     if(runningOnUnix()) filePrefix = ".";
1090 
1091     String userConfigName =
1092       System.getProperty("user.home"+ Strings.getFileSep() + filePrefix
1093         + GATE_DOT_XML;
1094     return userConfigName;
1095   // getDefaultUserConfigFileName
1096 
1097   /**
1098    * Get the default path to the user's session file, which is used unless an
1099    * alternative name has been specified via system properties or
1100    * {@link #setUserSessionFile(File)}
1101    *
1102    @return the default user session file path.
1103    */
1104   public static String getDefaultUserSessionFileName() {
1105     String filePrefix = "";
1106     if(runningOnUnix()) filePrefix = ".";
1107 
1108     String userSessionName =
1109       System.getProperty("user.home"+ Strings.getFileSep() + filePrefix
1110         + GATE_DOT_SER;
1111 
1112     return userSessionName;
1113   // getUserSessionFileName
1114 
1115   /**
1116    * This method tries to guess if we are on a UNIX system. It does this by
1117    * checking the value of <TT>System.getProperty("file.separator")</TT>; if
1118    * this is "/" it concludes we are on UNIX. <B>This is obviously not a very
1119    * good idea in the general case, so nothing much should be made to depend on
1120    * this method (e.g. just naming of config file <TT>.gate.xml</TT> as
1121    * opposed to <TT>gate.xml</TT>)</B>.
1122    */
1123   public static boolean runningOnUnix() {
1124     return Strings.getFileSep().equals("/");
1125   // runningOnUnix
1126 
1127   /**
1128    * This method tries to guess if we are on a Mac OS X system.  It does this
1129    * by checking the value of <TT>System.getProperty("os.name")</TT>.  Note
1130    * that if this method returns true, {@link #runningOnUnix()} will also
1131    * return true (i.e. Mac is considered a Unix platform) but the reverse is
1132    * not necessarily the case.
1133    */
1134   public static boolean runningOnMac() {
1135     return System.getProperty("os.name").toLowerCase().startsWith("mac os x");
1136   }
1137 
1138   /**
1139    * Returns the list of CREOLE directories the system knows about (either
1140    * pre-installed plugins in the plugins directory or CREOLE directories that
1141    * have previously been loaded manually).
1142    *
1143    @return a {@link List} of {@link URL}s.
1144    */
1145   public static List<URL> getKnownPlugins() {
1146     return knownPlugins;
1147   }
1148 
1149   /**
1150    * Adds the plugin to the list of known plugins.
1151    *
1152    @param pluginURL
1153    *          the URL for the new plugin.
1154    */
1155   public static void addKnownPlugin(URL pluginURL) {
1156     pluginURL = normaliseCreoleUrl(pluginURL);
1157     if(knownPlugins.contains(pluginURL)) return;
1158     knownPlugins.add(pluginURL);
1159   }
1160 
1161   /**
1162    * Makes sure the provided URL ends with "/" (CREOLE URLs always point to
1163    * directories so thry should always end with a slash.
1164    *
1165    @param url
1166    *          the URL to be normalised
1167    @return the (maybe) corrected URL.
1168    */
1169   public static URL normaliseCreoleUrl(URL url) {
1170     // CREOLE URLs are directory URLs so they should end with "/"
1171     String urlName = url.toExternalForm();
1172     String separator = "/";
1173     if(urlName.endsWith(separator)) {
1174       return url;
1175     }
1176     else {
1177       urlName += separator;
1178       try {
1179         return new URL(urlName);
1180       }
1181       catch(MalformedURLException mue) {
1182         throw new GateRuntimeException(mue);
1183       }
1184     }
1185   }
1186 
1187   /**
1188    * Returns the list of CREOLE directories the system loads automatically at
1189    * start-up.
1190    *
1191    @return a {@link List} of {@link URL}s.
1192    */
1193   public static List<URL> getAutoloadPlugins() {
1194     return autoloadPlugins;
1195   }
1196 
1197   /**
1198    * Adds a new directory to the list of plugins that are loaded automatically
1199    * at start-up.
1200    *
1201    @param pluginUrl
1202    *          the URL for the new plugin.
1203    */
1204   public static void addAutoloadPlugin(URL pluginUrl) {
1205     pluginUrl = normaliseCreoleUrl(pluginUrl);
1206     if(autoloadPlugins.contains(pluginUrl)) return;
1207     // make sure it's known
1208     addKnownPlugin(pluginUrl);
1209     // add it to autoload list
1210     autoloadPlugins.add(pluginUrl);
1211   }
1212 
1213   /**
1214    * Gets the information about a known directory.
1215    *
1216    @param directory
1217    *          the URL for the directory in question.
1218    @return a {@link DirectoryInfo} value.
1219    */
1220   public static DirectoryInfo getDirectoryInfo(URL directory) {
1221     directory = normaliseCreoleUrl(directory);
1222     if(!knownPlugins.contains(directory)) return null;
1223     DirectoryInfo dInfo = pluginData.get(directory);
1224     if(dInfo == null) {
1225       dInfo = new DirectoryInfo(directory);
1226       pluginData.put(directory, dInfo);
1227     }
1228     return dInfo;
1229   }
1230   
1231   /**
1232    * Returns information about plugin directories which provide the requested
1233    * resource
1234    
1235    @param resourceClassName
1236    *          the class name of the resource you are interested in
1237    @return information about the directories which provide an implementation
1238    *         of the requested resource
1239    */
1240   public static Set<DirectoryInfo> getDirectoryInfo(String resourceClassName) {
1241     Set<DirectoryInfo> dirs = new HashSet<DirectoryInfo>();
1242     
1243     for (URL url : knownPlugins) {
1244       DirectoryInfo dInfo = getDirectoryInfo(url);
1245       
1246       for (ResourceInfo rInfo : dInfo.getResourceInfoList()) {
1247         if (rInfo.resourceClassName.equals(resourceClassName)) {
1248           dirs.add(dInfo);
1249         }
1250       }
1251     }
1252     
1253     return dirs;
1254   }
1255 
1256   /**
1257    * Tells the system to &quot;forget&quot; about one previously known
1258    * directory. If the specified directory was loaded, it will be unloaded as
1259    * well - i.e. all the metadata relating to resources defined by this
1260    * directory will be removed from memory.
1261    *
1262    @param pluginURL
1263    */
1264   public static void removeKnownPlugin(URL pluginURL) {
1265     pluginURL = normaliseCreoleUrl(pluginURL);
1266     knownPlugins.remove(pluginURL);
1267     autoloadPlugins.remove(pluginURL);
1268     creoleRegister.removeDirectory(pluginURL);
1269     pluginData.remove(pluginURL);
1270   }
1271 
1272   /**
1273    * Tells the system to remove a plugin URL from the list of plugins that are
1274    * loaded automatically at system start-up. This will be reflected in the
1275    * user's configuration data file.
1276    *
1277    @param pluginURL
1278    *          the URL to be removed.
1279    */
1280   public static void removeAutoloadPlugin(URL pluginURL) {
1281     pluginURL = normaliseCreoleUrl(pluginURL);
1282     autoloadPlugins.remove(pluginURL);
1283   }
1284 
1285   /**
1286    * Stores information about the contents of a CREOLE directory.
1287    */
1288   public static class DirectoryInfo {
1289     
1290     private String name, html;
1291     
1292     private boolean core, remote;
1293     
1294     public boolean isCorePlugin() {
1295       return core;
1296     }
1297     
1298     public boolean isRemotePlugin() {
1299       return remote;
1300     }
1301     
1302     public boolean isUserPlugin() {
1303       File userPluginsHome = PluginUpdateManager.getUserPluginsHome();
1304       return (userPluginsHome != null
1305               && getUrl().toString()
1306                       .startsWith(userPluginsHome.toURI().toString()));
1307     }
1308     
1309     public String toHTMLString() {
1310       return html;
1311     }
1312     
1313     public DirectoryInfo(URL url) {
1314       this.url = normaliseCreoleUrl(url);
1315       valid = true;
1316       resourceInfoList = new ArrayList<ResourceInfo>();
1317       // this may invalidate it if something goes wrong
1318       parseCreole();
1319 
1320       remote = !this.url.getProtocol().equalsIgnoreCase("file");
1321       
1322       if (Gate.isSandboxed() || Gate.getPluginsHome() == null) {
1323         core = false;
1324       }
1325       else {
1326         core = !remote && (url.toString().startsWith(Gate.getPluginsHome().toURI().toString()));
1327       }
1328 
1329       html =
1330               "<html><body>"
1331                       + getName()
1332                       "<br><span style='font-size: 80%;'>"
1333                       (remote ? this.url.toString() : Files.fileFromURL(
1334                               this.url).getAbsolutePath())
1335                       "</span></body></html>";
1336     }
1337 
1338     public String getName() {
1339       if(name != nullreturn name;
1340 
1341       // url.getPath() works for jar URLs; url.toURI().getPath() doesn't
1342       // because jars aren't considered "hierarchical"
1343       name = url.getPath();
1344       if(name.endsWith("/")) {
1345         name = name.substring(0, name.length() 1);
1346       }
1347       int lastSlash = name.lastIndexOf("/");
1348       if(lastSlash != -1) {
1349         name = name.substring(lastSlash + 1);
1350       }
1351       try {
1352         // convert to (relative) URI and extract path.  This will
1353         // decode any %20 escapes in the name.
1354         name = new URI(name).getPath();
1355       catch(URISyntaxException ex) {
1356         // ignore, this should have been checked when adding the URL!
1357       }
1358       return name;
1359     }
1360     
1361     /**
1362      * Performs a shallow parse of the creole.xml file to get the information
1363      * about the resources contained.
1364      */
1365     protected void parseCreole() {
1366       SAXBuilder builder = new SAXBuilder(false);
1367       try {
1368         URL creoleFileURL = new URL(url, "creole.xml");
1369         org.jdom.Document creoleDoc = builder.build(creoleFileURL);
1370 
1371         final Map<String, ResourceInfo> resInfos = new LinkedHashMap<String, ResourceInfo>();
1372         List<Element> jobsList = new ArrayList<Element>();
1373         List<String> jarsToScan = new ArrayList<String>();
1374         List<String> allJars = new ArrayList<String>();
1375         jobsList.add(creoleDoc.getRootElement());
1376         while(!jobsList.isEmpty()) {
1377           Element currentElem = jobsList.remove(0);
1378           if(currentElem.getName().equalsIgnoreCase("JAR")) {
1379             @SuppressWarnings("unchecked")
1380             List<Attribute> attrs = currentElem.getAttributes();
1381             Iterator<Attribute> attrsIt = attrs.iterator();
1382             while(attrsIt.hasNext()) {
1383               Attribute attr = attrsIt.next();
1384               if(attr.getName().equalsIgnoreCase("SCAN"&& attr.getBooleanValue()) {
1385                 jarsToScan.add(currentElem.getTextTrim());
1386                 break;
1387               }
1388             }
1389             allJars.add(currentElem.getTextTrim());
1390           }
1391           else if(currentElem.getName().equalsIgnoreCase("RESOURCE")) {
1392             // we don't go deeper than resources so no recursion here
1393             String resName = currentElem.getChildTextTrim("NAME");
1394             String resClass = currentElem.getChildTextTrim("CLASS");
1395             String resComment = currentElem.getChildTextTrim("COMMENT");
1396             if(!resInfos.containsKey(resClass)) {
1397               // create the handler
1398               ResourceInfo rHandler =
1399                 new ResourceInfo(resName, resClass, resComment);
1400               resInfos.put(resClass, rHandler);
1401             }
1402           }
1403           else {
1404             // this is some higher level element -> simulate recursion
1405             // we want Depth-first-search so we need to add at the beginning
1406             @SuppressWarnings("unchecked")
1407             List<Element> newJobsList = new ArrayList<Element>(currentElem.getChildren());
1408             newJobsList.addAll(jobsList);
1409             jobsList = newJobsList;
1410           }
1411         }
1412 
1413         // now process the jar files with SCAN="true", looking for any extra
1414         // CreoleResource annotated classes.
1415         for(String jarFile : jarsToScan) {
1416           URL jarUrl = new URL(url, jarFile);
1417           scanJar(jarUrl, resInfos);
1418         }
1419 
1420         // see whether any of the ResourceInfo objects are still incomplete
1421         // (don't have a name)
1422         List<ResourceInfo> incompleteResInfos = new ArrayList<ResourceInfo>();
1423         for(ResourceInfo ri : resInfos.values()) {
1424           if(ri.getResourceName() == null) {
1425             incompleteResInfos.add(ri);
1426           }
1427         }
1428 
1429         if(!incompleteResInfos.isEmpty()) {
1430           fillInResInfos(incompleteResInfos, allJars);
1431         }
1432 
1433         // if any of the resource infos still don't have a name, take it from
1434         // the class name.
1435         for(ResourceInfo ri : incompleteResInfos) {
1436           if(ri.getResourceName() == null) {
1437             ri.resourceName = ri.resourceClassName.substring(
1438                     ri.resourceClassName.lastIndexOf('.'1);
1439           }
1440         }
1441 
1442         // finally, we have the complete list of ResourceInfos
1443         resourceInfoList.addAll(resInfos.values());
1444       }
1445       catch(IOException ioe) {
1446         valid = false;
1447         log.error("Problem while parsing plugin " + url.toExternalForm() "!\n"
1448           + ioe.toString() "\nPlugin not available!");
1449       }
1450       catch(JDOMException jde) {
1451         valid = false;
1452         log.error("Problem while parsing plugin " + url.toExternalForm() "!\n"
1453           + jde.toString() "\nPlugin not available!");
1454       }
1455     }
1456 
1457     protected void scanJar(URL jarUrl, Map<String, ResourceInfo> resInfosthrows IOException {
1458       JarInputStream jarInput = new JarInputStream(jarUrl.openStream()false);
1459       JarEntry entry = null;
1460       while((entry = jarInput.getNextJarEntry()) != null) {
1461         String entryName = entry.getName();
1462         if(entryName != null && entryName.endsWith(".class")) {
1463           final String className = entryName.substring(0,
1464                    entryName.length() 6).replace('/''.');
1465           if(!resInfos.containsKey(className)) {
1466             ClassReader classReader = new ClassReader(jarInput);
1467             ResourceInfo resInfo = new ResourceInfo(null, className, null);
1468             ResourceInfoVisitor visitor = new ResourceInfoVisitor(resInfo);
1469 
1470             classReader.accept(visitor, ClassReader.SKIP_CODE |
1471                 ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES);
1472             if(visitor.isCreoleResource()) {
1473               resInfos.put(className, resInfo);
1474             }
1475           }
1476         }
1477       }
1478 
1479       jarInput.close();
1480     }
1481 
1482     protected void fillInResInfos(List<ResourceInfo> incompleteResInfos,
1483               List<String> allJarsthrows IOException {
1484       // now create a temporary class loader with all the JARs (scanned or
1485       // not), so we can look up all the referenced classes in the normal
1486       // way and read their CreoleResource annotations (if any).
1487       URL[] jarUrls = new URL[allJars.size()];
1488       for(int i = 0; i < jarUrls.length; i++) {
1489         jarUrls[inew URL(url, allJars.get(i));
1490       }
1491       ClassLoader tempClassLoader = new URLClassLoader(jarUrls,
1492                 Gate.class.getClassLoader());
1493       for(ResourceInfo ri : incompleteResInfos) {
1494         String classFile = ri.getResourceClassName().replace('.''/')
1495                 ".class";
1496         InputStream classStream = tempClassLoader.getResourceAsStream(classFile);
1497         if(classStream != null) {
1498           ClassReader classReader = new ClassReader(classStream);
1499           ClassVisitor visitor = new ResourceInfoVisitor(ri);
1500           classReader.accept(visitor, ClassReader.SKIP_CODE |
1501               ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES);
1502           classStream.close();
1503         }
1504       }
1505     }
1506 
1507     /**
1508      @return Returns the resourceInfoList.
1509      */
1510     public List<ResourceInfo> getResourceInfoList() {
1511       return resourceInfoList;
1512     }
1513 
1514     /**
1515      @return Returns the url.
1516      */
1517     public URL getUrl() {
1518       return url;
1519     }
1520 
1521     /**
1522      @return Returns the valid.
1523      */
1524     public boolean isValid() {
1525       return valid;
1526     }
1527 
1528     /**
1529      * The URL for the CREOLE directory.
1530      */
1531     protected URL url;
1532 
1533     /**
1534      * Is the directory valid (i.e. is the location reachable and the creole.xml
1535      * file parsable).
1536      */
1537     protected boolean valid;
1538 
1539     /**
1540      * The list of {@link Gate.ResourceInfo} objects.
1541      */
1542     protected List<ResourceInfo> resourceInfoList;
1543   }
1544 
1545   /**
1546    * Stores information about a resource defined by a CREOLE directory. The
1547    * resource might not have been loaded in the system so not all information
1548    * normally provided by the {@link ResourceData} class is available. This is
1549    * what makes this class different from {@link ResourceData}.
1550    */
1551   public static class ResourceInfo {
1552     public ResourceInfo(String name, String className, String comment) {
1553       this.resourceClassName = className;
1554       this.resourceName = name;
1555       this.resourceComment = comment;
1556     }
1557 
1558     /**
1559      @return Returns the resourceClassName.
1560      */
1561     public String getResourceClassName() {
1562       return resourceClassName;
1563     }
1564 
1565     /**
1566      @return Returns the resourceComment.
1567      */
1568     public String getResourceComment() {
1569       return resourceComment;
1570     }
1571 
1572     /**
1573      @return Returns the resourceName.
1574      */
1575     public String getResourceName() {
1576       return resourceName;
1577     }
1578 
1579     /**
1580      * The class for the resource.
1581      */
1582     protected String resourceClassName;
1583 
1584     /**
1585      * The resource name.
1586      */
1587     protected String resourceName;
1588 
1589     /**
1590      * The comment for the resource.
1591      */
1592     protected String resourceComment;
1593   }
1594 
1595   /**
1596    * ClassVisitor that uses information from a CreoleResource annotation on the
1597    * visited class (if such exists) to fill in the name and comment in the
1598    * corresponding ResourceInfo.
1599    */
1600   private static class ResourceInfoVisitor extends EmptyVisitor {
1601     private ResourceInfo resInfo;
1602 
1603     private boolean foundCreoleResource = false;
1604 
1605     private boolean isAbstract = false;
1606 
1607     public ResourceInfoVisitor(ResourceInfo resInfo) {
1608       this.resInfo = resInfo;
1609     }
1610 
1611     public boolean isCreoleResource() {
1612       return foundCreoleResource && !isAbstract;
1613     }
1614 
1615     /**
1616      * Type descriptor for the CreoleResource annotation type.
1617      */
1618     private static final String CREOLE_RESOURCE_DESC =
1619             Type.getDescriptor(CreoleResource.class);
1620 
1621     /**
1622      * Visit the class header, checking whether this is an abstract class or
1623      * interface and setting the isAbstract flag appropriately.
1624      */
1625     @Override
1626     public void visit(int version, int access, String name, String signature,
1627             String superName, String[] interfaces) {
1628       isAbstract = ((access &
1629             (Opcodes.ACC_INTERFACE | Opcodes.ACC_ABSTRACT)) != 0);
1630     }
1631 
1632     /**
1633      * Visit an annotation on the class.
1634      */
1635     @Override
1636     public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
1637       // we've found a CreoleResource annotation on this class
1638       if(desc.equals(CREOLE_RESOURCE_DESC)) {
1639         foundCreoleResource = true;
1640         return new AnnotationVisitor(Opcodes.ASM5) {
1641           @Override
1642           public void visit(String name, Object value) {
1643             if(name.equals("name"&& resInfo.resourceName == null) {
1644               resInfo.resourceName = (String)value;
1645             }
1646             else if(name.equals("comment"&& resInfo.resourceComment == null) {
1647               resInfo.resourceComment = (String)value;
1648             }
1649           }
1650 
1651           @Override
1652           public AnnotationVisitor visitAnnotation(String name,
1653                     String desc) {
1654             // don't want to recurse into AutoInstance annotations
1655             return this;
1656           }
1657         };
1658       }
1659       else {
1660         return super.visitAnnotation(desc, visible);
1661       }
1662     }
1663   }
1664 
1665   /**
1666    * The top level directory of the GATE installation.
1667    */
1668   protected static File gateHome;
1669 
1670   /** Site config file */
1671   private static File siteConfigFile;
1672 
1673   /** User config file */
1674   private static File userConfigFile;
1675 
1676   /**
1677    * The top level directory for GATE installed plugins.
1678    */
1679   protected static File pluginsHome;
1680 
1681   /**
1682    * The "builtin" creole directory URL, where the creole.xml that defines
1683    * things like DocumentImpl can be found.
1684    */
1685   protected static URL builtinCreoleDir;
1686 
1687   /**
1688    * The user session file to use.
1689    */
1690   protected static File userSessionFile;
1691 
1692   /**
1693    * Set the location of the GATE home directory.
1694    *
1695    @throws IllegalStateException
1696    *           if the value has already been set.
1697    */
1698   public static void setGateHome(File gateHome) {
1699     if(Gate.gateHome != null) { throw new IllegalStateException(
1700       "gateHome has already been set")}
1701     Gate.gateHome = gateHome;
1702   }
1703 
1704   /**
1705    * Set the location of the plugins directory.
1706    *
1707    @throws IllegalStateException
1708    *           if the value has already been set.
1709    */
1710   public static void setPluginsHome(File pluginsHome) {
1711     if(Gate.pluginsHome != null) { throw new IllegalStateException(
1712       "pluginsHome has already been set")}
1713     Gate.pluginsHome = pluginsHome;
1714   }
1715 
1716   /**
1717    * Get the location of the plugins directory.
1718    *
1719    @return the plugins drectory, or null if this has not yet been set (i.e.
1720    *         <code>Gate.init()</code> has not yet been called).
1721    */
1722   public static File getPluginsHome() {
1723     return pluginsHome;
1724   }
1725 
1726   /**
1727    * Set the location of the user's config file.
1728    *
1729    @throws IllegalStateException
1730    *           if the value has already been set.
1731    */
1732   public static void setUserConfigFile(File userConfigFile) {
1733     if(Gate.userConfigFile != null) { throw new IllegalStateException(
1734       "userConfigFile has already been set")}
1735     Gate.userConfigFile = userConfigFile;
1736   }
1737 
1738   /**
1739    * Get the location of the user's config file.
1740    *
1741    @return the user config file, or null if this has not yet been set (i.e.
1742    *         <code>Gate.init()</code> has not yet been called).
1743    */
1744   public static File getUserConfigFile() {
1745     return userConfigFile;
1746   }
1747 
1748   /**
1749    * Set the URL to the "builtin" creole directory. The URL must point to a
1750    * directory, and must end with a forward slash.
1751    *
1752    @throws IllegalStateException
1753    *           if the value has already been set.
1754    */
1755   public static void setBuiltinCreoleDir(URL builtinCreoleDir) {
1756     if(Gate.builtinCreoleDir != null) { throw new IllegalStateException(
1757       "builtinCreoleDir has already been set")}
1758     Gate.builtinCreoleDir = builtinCreoleDir;
1759   }
1760 
1761   /**
1762    * Get the URL to the "builtin" creole directory, i.e. the directory that
1763    * contains the creole.xml file that defines things like DocumentImpl, the
1764    * Controllers, etc.
1765    */
1766   public static URL getBuiltinCreoleDir() {
1767     return builtinCreoleDir;
1768   }
1769 
1770   /**
1771    * Set the user session file. This can only done prior to calling Gate.init()
1772    * which will set the file to either the OS-specific default or whatever has
1773    * been set by the property gate.user.session
1774    *
1775    @throws IllegalStateException
1776    *           if the value has already been set.
1777    */
1778   public static void setUserSessionFile(File newUserSessionFile) {
1779     if(Gate.userSessionFile != null) { throw new IllegalStateException(
1780       "userSessionFile has already been set")}
1781     Gate.userSessionFile = newUserSessionFile;
1782   }
1783 
1784   /**
1785    * Get the user session file.
1786    *
1787    @return the file corresponding to the user session file or null, if not yet
1788    *         set.
1789    */
1790   public static File getUserSessionFile() {
1791     return userSessionFile;
1792   }
1793 
1794   /**
1795    * The list of plugins (aka CREOLE directories) the system knows about. This
1796    * list contains URL objects.
1797    */
1798   private static List<URL> knownPlugins;
1799 
1800   /**
1801    * The list of plugins (aka CREOLE directories) the system loads automatically
1802    * at start-up. This list contains URL objects.
1803    */
1804   protected static List<URL> autoloadPlugins;
1805 
1806   /**
1807    * Map from URL of directory to {@link DirectoryInfo}.
1808    */
1809   protected static Map<URL, DirectoryInfo> pluginData;
1810 
1811   /**
1812    * Flag for whether to use native serialization or xml serialization when
1813    * saving applications.
1814    */
1815   private static boolean useXMLSerialization = true;
1816 
1817   /**
1818    * Tell GATE whether to use XML serialization for applications.
1819    */
1820   public static void setUseXMLSerialization(boolean useXMLSerialization) {
1821     Gate.useXMLSerialization = useXMLSerialization;
1822   }
1823 
1824   /**
1825    * Should we use XML serialization for applications.
1826    */
1827   public static boolean getUseXMLSerialization() {
1828     return useXMLSerialization;
1829   }
1830 
1831   /**
1832    * Returns the listeners map, a map that holds all the listeners that
1833    * are singletons (e.g. the status listener that updates the status
1834    * bar on the main frame or the progress listener that updates the
1835    * progress bar on the main frame). The keys used are the class names
1836    * of the listener interface and the values are the actual listeners
1837    * (e.g "gate.event.StatusListener" -> this). The returned map is the
1838    * actual data member used to store the listeners so any changes in
1839    * this map will be visible to everyone.
1840    @return the listeners map
1841    */
1842   public static java.util.Map<String, EventListener> getListeners() {
1843     return listeners;
1844   }
1845 
1846   /**
1847    * A Map which holds listeners that are singletons (e.g. the status
1848    * listener that updates the status bar on the main frame or the
1849    * progress listener that updates the progress bar on the main frame).
1850    * The keys used are the class names of the listener interface and the
1851    * values are the actual listeners (e.g "gate.event.StatusListener" ->
1852    * this).
1853    */
1854   private static final java.util.Map<String, EventListener> listeners =
1855     new HashMap<String, EventListener>();
1856 
1857 // class Gate