1   /*
2    *  Gate.java
3    *
4    *  Copyright (c) 1998-2004, The University of Sheffield.
5    *
6    *  This file is part of GATE (see http://gate.ac.uk/), and is free
7    *  software, licenced under the GNU Library General Public License,
8    *  Version 2, June 1991 (in the distribution as file licence.html,
9    *  and also available at http://gate.ac.uk/gate/licence.html).
10   *
11   *  Hamish Cunningham, 31/07/98
12   *
13   *  $Id: Gate.java,v 1.68 2004/08/04 16:58:42 valyt Exp $
14   */
15  
16  package gate;
17  
18  import java.io.*;
19  import java.net.*;
20  import java.util.*;
21  import org.jdom.Element;
22  import org.jdom.JDOMException;
23  import org.jdom.input.SAXBuilder;
24  
25  import gate.config.ConfigDataProcessor;
26  import gate.creole.CreoleRegisterImpl;
27  import gate.creole.ResourceData;
28  import gate.event.CreoleListener;
29  import gate.util.*;
30  
31  /** The class is responsible for initialising the GATE libraries, and
32    * providing access to singleton utility objects, such as the GATE class
33    * loader, CREOLE register and so on.
34    */
35  public class Gate implements GateConstants
36  {
37    /** Debug flag */
38    private static final boolean DEBUG = false;
39  
40    /**
41     *  The default StringBuffer size, it seems that we need longer string
42     *  than the StringBuffer class default because of the high number of
43     *  buffer expansions
44     *  */
45    public static final int STRINGBUFFER_SIZE = 1024;
46  
47    /**
48     *  The default size to be used for Hashtable, HashMap and HashSet.
49     *  The defualt is 11 and it leads to big memory usage. Having a default
50     *  load factor of 0.75, table of size 4 can take 3 elements before being
51     *  re-hashed - a values that seems to be optimal for most of the cases.
52     *  */
53    public static final int HASH_STH_SIZE = 4;
54  
55  
56    /**
57     *  The database schema owner (GATEADMIN is default)
58     *  this one should not be hardcoded but set in the
59     *  XML initialization files
60     *
61     *  */
62    public static final String DB_OWNER = "gateadmin";
63  
64  
65    /** The list of builtin URLs to search for CREOLE resources. */
66    private static String builtinCreoleDirectoryUrls[] = {
67      // "http://derwent.dcs.shef.ac.uk/gate.ac.uk/creole/"
68  
69      // this has been moved to initCreoleRegister and made relative to
70      // the base URL returned by getUrl()
71      // "http://gate.ac.uk/creole/"
72    };
73  
74  
75    /** The GATE URI used to interpret custom GATE tags*/
76    public static final String URI = "http://www.gate.ac.uk";
77  
78    /** Minimum version of JDK we support */
79    protected static final String MIN_JDK_VERSION = "1.4";
80  
81    /** Get the minimum supported version of the JDK */
82    public static String getMinJdkVersion() { return MIN_JDK_VERSION; }
83  
84    /** Initialisation - must be called by all clients before using
85      * any other parts of the library. Also initialises the CREOLE
86      * register and reads config data (<TT>gate.xml</TT> files).
87      * @see #initCreoleRegister
88      */
89    public static void init() throws GateException {
90  
91      // register the URL handler  for the "gate://" URLs
92      System.setProperty(
93        "java.protocol.handler.pkgs",
94        System.getProperty("java.protocol.handler.pkgs")
95          + "|" + "gate.util.protocols"
96      );
97  
98      //System.setProperty("javax.xml.parsers.SAXParserFactory",
99        //                       "org.apache.xerces.jaxp.SAXParserFactoryImpl");
100 
101     //initialise the symbols generator
102     lastSym = 0;
103 
104     // create class loader and creole register if they're null
105     if(classLoader == null)
106       classLoader = new GateClassLoader(Gate.class.getClassLoader());
107     if(creoleRegister == null)
108       creoleRegister = new CreoleRegisterImpl();
109     if(knownPlugins == null) knownPlugins = new ArrayList();
110     if(autoloadPlugins == null) autoloadPlugins = new ArrayList();
111     if(pluginData == null) pluginData = new HashMap();
112     // init the creole register
113     initCreoleRegister();
114     // init the data store register
115     initDataStoreRegister();
116     // read gate.xml files; this must come before creole register
117     // initialisation in order for the CREOLE-DIR elements to have and effect
118     initConfigData();
119     
120     initCreoleRepositories();
121     // the creoleRegister acts as a proxy for datastore related events
122     dataStoreRegister.addCreoleListener(creoleRegister);
123 
124     // some of the events are actually fired by the {@link gate.Factory}
125     Factory.addCreoleListener(creoleRegister);
126 
127     // check we have a useable JDK
128     if(System.getProperty("java.version").compareTo(MIN_JDK_VERSION) < 0) {
129       throw new GateException(
130         "GATE requires JDK " + MIN_JDK_VERSION + " or newer"
131       );
132     }
133 
134     //register Lucene as a IR search engine
135     try{
136       registerIREngine("gate.creole.ir.lucene.LuceneIREngine");
137     }catch(ClassNotFoundException cnfe){
138       throw new GateRuntimeException(cnfe);
139     }
140   } // init()
141   
142   /**
143    * Loads the CREOLE repositories (aka plugins) that the user has selected for 
144    * automatic loading.
145    * Loads the information about known plugins in memory.
146    */
147   protected static void initCreoleRepositories(){
148     //the logic is:
149     //get the list of know plugins from gate.xml
150     //add all the installed plugins
151     //get the list of loadable plugins 
152     //or use ANNIE if value not set
153     //load loadable plugins
154     
155     //process the known plugins list
156     String knownPluginsPath = (String)getUserConfig().get(KNOWN_PLUGIN_PATH_KEY);
157     if(knownPluginsPath != null && knownPluginsPath.length() > 0){
158       StringTokenizer strTok = new StringTokenizer(knownPluginsPath, ";", false);
159       while(strTok.hasMoreTokens()){
160         String aKnownPluginPath = strTok.nextToken();
161         try{
162           URL aPluginURL = new URL(aKnownPluginPath);
163           addKnownPlugin(aPluginURL);
164         }catch(MalformedURLException mue){
165           Err.prln("Plugin error: " + aKnownPluginPath + " is an invalid URL!");
166         }
167       }
168     }
169     //add all the installed plugins
170     File pluginsHome = new File(System.getProperty(GATE_HOME_SYSPROP_KEY), 
171             "plugins");
172     File[] dirs = pluginsHome.listFiles();
173     for(int i = 0; i < dirs.length; i++){
174       File creoleFile = new File(dirs[i], "creole.xml");
175       if(creoleFile.exists()){
176         try{
177           URL pluginURL = dirs[i].toURL();
178           if(!knownPlugins.contains(pluginURL)) 
179             knownPlugins.add(pluginURL);
180         }catch(MalformedURLException mue){
181           //this shoulod never happen
182           throw new GateRuntimeException(mue);
183         }
184       }
185     }
186     //we now have a full list of known plugins
187     //get the information about the plugins
188     Iterator pluginIter = knownPlugins.iterator();
189     while(pluginIter.hasNext()){
190       URL aPluginURL = (URL)pluginIter.next();
191       DirectoryInfo dInfo = new DirectoryInfo(aPluginURL);
192       pluginData.put(aPluginURL, dInfo);
193     }
194     
195     //process the autoload plugins
196     String pluginPath = getUserConfig().getString(LOAD_PLUGIN_PATH_KEY);
197     //can be overridden by system property
198     String prop = System.getProperty(LOAD_PLUGIN_PATH_SYSPROP_KEY);
199     if(prop != null && prop.length() > 0) pluginPath = prop;
200     
201     if(pluginPath == null || pluginPath.length() == 0){
202       //value not set -> use the default
203       try{
204         pluginPath = new File(pluginsHome, "ANNIE/").toURL().toString();
205         getUserConfig().put(LOAD_PLUGIN_PATH_KEY, pluginPath);
206       }catch(MalformedURLException mue){
207         throw new GateRuntimeException(mue);
208       }
209     }
210     
211     //load all loadable plugins
212     StringTokenizer strTok = new StringTokenizer(pluginPath, ";", false);
213     while(strTok.hasMoreTokens()){
214       String aDir = strTok.nextToken();
215       try{
216         URL aPluginURL = new URL(aDir);
217         if(!autoloadPlugins.contains(aPluginURL)){
218           autoloadPlugins.add(aPluginURL);
219         }
220         getCreoleRegister().registerDirectories(aPluginURL);
221       }catch(MalformedURLException mue){
222         System.err.println("Cannot load " + aDir + " CREOLE repository.");
223         mue.printStackTrace();
224       }catch(GateException ge){
225         System.err.println("Cannot load " + aDir + " CREOLE repository.");
226         ge.printStackTrace();
227       }
228     }
229   }
230 
231   /** Initialise the CREOLE register. */
232   public static void initCreoleRegister() throws GateException {
233 
234     // register the builtin CREOLE directories
235     for(int i=0; i<builtinCreoleDirectoryUrls.length; i++)
236       try {
237         creoleRegister.addDirectory(
238           new URL(builtinCreoleDirectoryUrls[i])
239         );
240       } catch(MalformedURLException e) {
241         throw new GateException(e);
242       }
243 
244 /*
245 We'll have to think about this. Right now it points to the creole inside the
246 jar/classpath so it's the same as registerBuiltins
247 */
248 //    // add the GATE base URL creole directory
249 //    creoleRegister.addDirectory(Gate.getUrl("creole/"));
250 //    creoleRegister.registerDirectories();
251 
252     // register the resources that are actually in gate.jar
253     creoleRegister.registerBuiltins();
254   } // initCreoleRegister
255 
256   /** Initialise the DataStore register. */
257   public static void initDataStoreRegister() {
258     dataStoreRegister = new DataStoreRegister();
259   } // initDataStoreRegister()
260 
261   /**
262    * Reads config data (<TT>gate.xml</TT> files). There are three
263    * sorts of these files:
264    * <UL>
265    * <LI>
266    * The builtin file from GATE's resources - this is read first.
267    * <LI>
268    * A site-wide init file given as a command-line argument or as a
269    * <TT>gate.config</TT> property - this is read second.
270    * <LI>
271    * The user's file from their home directory - this is read last.
272    * </UL>
273    * Settings from files read after some settings have already been
274    * made will simply overwrite the previous settings.
275    */
276   public static void initConfigData() throws GateException {
277     ConfigDataProcessor configProcessor = new ConfigDataProcessor();
278 
279     // url of the builtin config data (for error messages)
280     URL configUrl =
281       Gate.getClassLoader().getResource("gate/resources/" + GATE_DOT_XML);
282 
283     // open a stream to the builtin config data file and parse it
284     InputStream configStream = null;
285     try {
286       configStream = Files.getGateResourceAsStream(GATE_DOT_XML);
287     } catch(IOException e) {
288       throw new GateException(
289         "Couldn't open builtin config data file: " + configUrl + " " + e
290       );
291     }
292     configProcessor.parseConfigFile(configStream, configUrl);
293 
294     // parse any command-line initialisation file
295     File siteConfigFile = Gate.getSiteConfigFile();
296     if(siteConfigFile != null) {
297       try {
298         configUrl = siteConfigFile.toURL();
299         configStream = new FileInputStream(Gate.getSiteConfigFile());
300       } catch(IOException e) {
301         throw new GateException(
302           "Couldn't open site config data file: " + configUrl + " " + e
303         );
304       }
305       configProcessor.parseConfigFile(configStream, configUrl);
306     }
307 
308     // parse the user's config file (if it exists)
309     String userConfigName = getUserConfigFileName();
310     File userConfigFile = null;
311     URL userConfigUrl = null;
312     if(DEBUG) { Out.prln("loading user config from " + userConfigName); }
313     configStream = null;
314     boolean userConfigExists = true;
315     try {
316       userConfigFile = new File(userConfigName);
317       configStream = new FileInputStream(userConfigFile);
318       userConfigUrl = userConfigFile.toURL();
319     } catch(IOException e) {
320       userConfigExists = false;
321     }
322     if(userConfigExists)
323       configProcessor.parseConfigFile(configStream, userConfigUrl);
324 
325     // remember the init-time config options
326     originalUserConfig.putAll(userConfig);
327 
328     if(DEBUG) {
329       Out.prln(
330         "user config loaded; DBCONFIG=" + DataStoreRegister.getConfigData()
331       );
332     }
333     
334     
335     //get the CREOLE repositories to load if set trough a sys prop
336     String creolepath = System.getProperty("creole.path");
337     if(creolepath != null && creolepath.length() > 0){
338       StringTokenizer strTok = new StringTokenizer(creolepath, 
339               Strings.getPathSep(), false);
340       while(strTok.hasMoreTokens()){
341         String aPath = strTok.nextToken();
342         //this is an URL
343         try{
344           URL creoleURL = new File(aPath).toURL();
345           Gate.getCreoleRegister().registerDirectories(creoleURL);
346         }catch(MalformedURLException mue){
347           throw new GateRuntimeException(mue);
348         }
349       }
350     }
351     
352   } // initConfigData()
353 
354   /**
355    * Attempts to guess the Unicode font for the platform.
356    */
357   public static String guessUnicodeFont(){
358     //guess the Unicode font for the platform
359     String[] fontNames = java.awt.GraphicsEnvironment.
360                          getLocalGraphicsEnvironment().
361                          getAvailableFontFamilyNames();
362     String unicodeFontName = null;
363     for(int i = 0; i < fontNames.length; i++){
364       if(fontNames[i].equalsIgnoreCase("Arial Unicode MS")){
365         unicodeFontName = fontNames[i];
366         break;
367       }
368       if(fontNames[i].toLowerCase().indexOf("unicode") != -1){
369         unicodeFontName = fontNames[i];
370       }
371     }//for(int i = 0; i < fontNames.length; i++)
372     return unicodeFontName;
373   }
374 
375   /** Get a URL that points to either an HTTP server or a file system
376     * that contains GATE files (such as test cases). The following locations
377     * are tried in sequence:
378     * <UL>
379     * <LI>
380     * <TT>http://derwent.dcs.shef.ac.uk/gate.ac.uk/</TT>, a Sheffield-internal
381     * development server (the gate.ac.uk affix is a copy of the file system
382     * present on GATE's main public server - see next item);
383     * <LI>
384     * <TT>http://gate.ac.uk/</TT>, GATE's main public server;
385     * <LI>
386     * <TT>http://localhost/gate.ac.uk/</TT>, a Web server running on the
387     * local machine;
388     * <LI>
389     * the local file system where the binaries for the
390     * current invocation of GATE are stored.
391     * </UL>
392     * In each case we assume that a Web server will be running on port 80,
393     * and that if we can open a socket to that port then the server is
394     * running. (This is a bit of a strong assumption, but this URL is used
395     * largely by the test suite, so we're not betting anything too critical
396     * on it.)
397     * <P>
398     * Note that the value returned will only be calculated when the existing
399     * value recorded by this class is null (which will be the case when
400     * neither setUrlBase nor getUrlBase have been called, or if
401     * setUrlBase(null) has been called).
402     */
403   public static URL getUrl() throws GateException {
404     if(urlBase != null) return urlBase;
405 
406     try {
407 
408        // if we're assuming a net connection, try network servers
409       if(isNetConnected()) {
410         if(
411           tryNetServer("gate-internal.dcs.shef.ac.uk", 80, "/") ||
412    //       tryNetServer("derwent.dcs.shef.ac.uk", 80, "/gate.ac.uk/") ||
413           tryNetServer("gate.ac.uk", 80, "/")
414         ) {
415             if(DEBUG) Out.prln("getUrl() returned " + urlBase);
416             return urlBase;
417         }
418       } // if isNetConnected() ...
419 
420       // no network servers; try for a local host web server.
421       // we use InetAddress to get host name instead of using "localhost" coz
422       // badly configured Windoze IP sometimes doesn't resolve the latter
423       if(
424         isLocalWebServer() &&
425         tryNetServer(
426           InetAddress.getLocalHost().getHostName(), 80, "/gate.ac.uk/"
427         )
428       ) {
429         if(DEBUG) Out.prln("getUrlBase() returned " + urlBase);
430         return urlBase;
431       }
432 
433       // try the local file system
434       tryFileSystem();
435 
436     } catch(MalformedURLException e) {
437       throw new GateException("Bad URL, getUrlBase(): " + urlBase + ": " + e);
438     } catch(UnknownHostException e) {
439       throw new GateException("No host, getUrlBase(): " + urlBase + ": " + e);
440     }
441 
442     // return value will be based on the file system, or null
443     if(DEBUG) Out.prln("getUrlBase() returned " + urlBase);
444     return urlBase;
445   } // getUrl()
446 
447   /** Get a URL that points to either an HTTP server or a file system
448     * that contains GATE files (such as test cases).
449     * Calls <TT>getUrl()</TT> then adds the <TT>path</TT> parameter to
450     * the result.
451     * @param path a path to add to the base URL.
452     * @see #getUrl()
453     */
454   public static URL getUrl(String path) throws GateException {
455     getUrl();
456     if(urlBase == null)
457       return null;
458 
459     URL newUrl = null;
460     try {
461       newUrl = new URL(urlBase, path);
462     } catch(MalformedURLException e) {
463       throw new GateException("Bad URL, getUrl( " + path + "): " + e);
464     }
465 
466     if(DEBUG) Out.prln("getUrl(" + path + ") returned " + newUrl);
467     return newUrl;
468   } // getUrl(path)
469 
470   /** Flag controlling whether we should try to access the net, e.g. when
471     * setting up a base URL.
472     */
473   private static boolean netConnected = true;
474 
475   private static int lastSym;
476 
477   /**
478    * A list of names of classes that implement {@link gate.creole.ir.IREngine}
479    * that will be used as information retrieval engines.
480    */
481   private static Set registeredIREngines = new HashSet();
482 
483   /**
484    * Registers a new IR engine. The class named should implement
485    * {@link gate.creole.ir.IREngine}.
486    * @param className the fully qualified name of the class to be registered
487    * @throws GateException if the class does not implement the
488    * {@link gate.creole.ir.IREngine} interface.
489    * @throws ClassNotFoundException if the named class cannot be found.
490    */
491   public static void registerIREngine(String className)
492     throws GateException, ClassNotFoundException{
493     Class aClass = Class.forName(className);
494     if(gate.creole.ir.IREngine.class.isAssignableFrom(aClass)){
495       registeredIREngines.add(className);
496     }else{
497       throw new GateException(className + " does not implement the " +
498                               gate.creole.ir.IREngine.class.getName() +
499                               " interface!");
500     }
501   }
502 
503   /**
504    * Unregisters a previously registered IR engine.
505    * @param className the name of the class to be removed from the list of
506    * registered IR engines.
507    * @return true if the class was found and removed.
508    */
509   public static boolean unregisterIREngine(String className){
510     return registeredIREngines.remove(className);
511   }
512 
513   /**
514    * Gets the set of registered IR engines.
515    * @return an unmodifiable {@link java.util.Set} value.
516    */
517   public static Set getRegisteredIREngines(){
518     return Collections.unmodifiableSet(registeredIREngines);
519   }
520 
521   /** Should we assume we're connected to the net? */
522   public static boolean isNetConnected() { return netConnected; }
523 
524   /**
525    * Tell GATE whether to assume we're connected to the net. Has to be
526    * called <B>before</B> {@link #init()}.
527    */
528   public static void setNetConnected(boolean b) { netConnected = b; }
529 
530   /**
531    * Flag controlling whether we should try to access a web server on
532    * localhost, e.g. when setting up a base URL. Has to be
533    * called <B>before</B> {@link #init()}.
534    */
535   private static boolean localWebServer = true;
536 
537   /** Should we assume there's a local web server? */
538   public static boolean isLocalWebServer() { return localWebServer; }
539 
540   /** Tell GATE whether to assume there's a local web server. */
541   public static void setLocalWebServer(boolean b) { localWebServer = b; }
542 
543   /** Try to contact a network server. When sucessfull sets urlBase to an HTTP
544     * URL for the server.
545     * @param hostName the name of the host to try and connect to
546     * @param serverPort the port to try and connect to
547     * @param path a path to append to the URL when we make a successfull
548     * connection. E.g. for host xyz, port 80, path /thing, the resultant URL
549     * would be <TT>http://xyz:80/thing</TT>.
550     */
551   public static boolean tryNetServer(
552     String hostName, int serverPort, String path
553   ) throws MalformedURLException {
554     Socket socket = null;
555     if(DEBUG)
556       Out.prln(
557         "tryNetServer(hostName=" + hostName + ", serverPort=" + serverPort +
558         ", path=" + path +")"
559       );
560 
561     // is the host listening at the port?
562     try{
563       URL url = new URL("http://" + hostName + ":" + serverPort + "/");
564       URLConnection uConn =  url.openConnection();
565       HttpURLConnection huConn = null;
566       if(uConn instanceof HttpURLConnection)
567         huConn = (HttpURLConnection)uConn;
568       if(huConn.getResponseCode() == -1) return false;
569     } catch (IOException e){
570       return false;
571     }
572 
573 //    if(socket != null) {
574       urlBase = new URL("http", hostName, serverPort, path);
575       return true;
576 //    }
577 
578 //    return false;
579   } // tryNetServer()
580 
581   /** Try to find GATE files in the local file system */
582   protected static boolean tryFileSystem() throws MalformedURLException {
583     String urlBaseName = locateGateFiles();
584     if(DEBUG) Out.prln("tryFileSystem: " + urlBaseName);
585 
586     urlBase = new URL(urlBaseName + "gate/resources/gate.ac.uk/");
587     return urlBase == null;
588   } // tryFileSystem()
589 
590   /**
591    * Find the location of the GATE binaries (and resources) in the
592    * local file system.
593    */
594   public static String locateGateFiles() {
595     String aGateResourceName = "gate/resources/creole/creole.xml";
596     URL resourcesUrl = Gate.getClassLoader().getResource(aGateResourceName);
597 
598     StringBuffer basePath = new StringBuffer(resourcesUrl.toExternalForm());
599     String urlBaseName =
600       basePath.substring(0, basePath.length() - aGateResourceName.length());
601 
602     return urlBaseName;
603   } // locateGateFiles
604 
605   /**
606    * Checks whether a particular class is a Gate defined type
607    */
608   public static boolean isGateType(String classname){
609     boolean res = getCreoleRegister().containsKey(classname);
610     if(!res){
611       try{
612         Class aClass = Class.forName(classname);
613         res = Resource.class.isAssignableFrom(aClass) ||
614               Controller.class.isAssignableFrom(aClass) ||
615               DataStore.class.isAssignableFrom(aClass);
616       }catch(ClassNotFoundException cnfe){
617         return false;
618       }
619     }
620     return res;
621   }
622 
623   /** Returns the value for the HIDDEN attribute of a feature map */
624   static public boolean getHiddenAttribute(FeatureMap fm){
625     if(fm == null) return false;
626     Object value = fm.get("gate.HIDDEN");
627     return value != null &&
628            value instanceof String &&
629            ((String)value).equals("true");
630   }
631 
632   /** Sets the value for the HIDDEN attribute of a feature map */
633   static public void setHiddenAttribute(FeatureMap fm, boolean hidden){
634     if(hidden){
635       fm.put("gate.HIDDEN", "true");
636     }else{
637       fm.remove("gate.HIDDEN");
638     }
639   }
640 
641 
642   /** Registers a {@link gate.event.CreoleListener} with the Gate system
643     */
644   public static synchronized void addCreoleListener(CreoleListener l){
645     creoleRegister.addCreoleListener(l);
646   } // addCreoleListener
647 
648   /** Set the URL base for GATE files, e.g. <TT>http://gate.ac.uk/</TT>. */
649   public static void setUrlBase(URL urlBase) { Gate.urlBase = urlBase; }
650 
651   /** The URL base for GATE files, e.g. <TT>http://gate.ac.uk/</TT>. */
652   private static URL urlBase = null;
653 
654   /** Class loader used e.g. for loading CREOLE modules, of compiling
655     * JAPE rule RHSs.
656     */
657   private static GateClassLoader classLoader = null;
658 
659   /** Get the GATE class loader. */
660   public static GateClassLoader getClassLoader() { return classLoader; }
661 
662   /** The CREOLE register. */
663   private static CreoleRegister creoleRegister = null;
664 
665   /** Get the CREOLE register. */
666   public static CreoleRegister getCreoleRegister() { return creoleRegister; }
667 
668   /** The DataStore register */
669   private static DataStoreRegister dataStoreRegister = null;
670 
671   /**
672    * The current executable under execution.
673    */
674   private static gate.Executable currentExecutable;
675 
676   /** Get the DataStore register. */
677   public static DataStoreRegister getDataStoreRegister() {
678     return dataStoreRegister;
679   } // getDataStoreRegister
680 
681   /**
682    * Sets the {@link Executable} currently under execution.
683    * At a given time there can be only one executable set. After the executable
684    * has finished its execution this value should be set back to null.
685    * An attempt to set the executable while this value is not null will result
686    * in the method call waiting until the old executable is set to null.
687    */
688   public synchronized static void setExecutable(gate.Executable executable) {
689     if(executable == null) currentExecutable = executable;
690     else{
691       while(getExecutable() != null){
692         try{
693           Thread.sleep(200);
694         }catch(InterruptedException ie){
695           throw new LuckyException(ie.toString());
696         }
697       }
698       currentExecutable = executable;
699     }
700   } // setExecutable
701 
702   /**
703    * Returns the curently set executable.
704    * @see #setExecutable(gate.Executable)
705    */
706   public synchronized static gate.Executable getExecutable() {
707     return currentExecutable;
708   } // getExecutable
709 
710 
711   /**
712    * Returns a new unique string
713    */
714   public synchronized static String genSym() {
715     StringBuffer buff = new StringBuffer(Integer.toHexString(lastSym++).
716                                          toUpperCase());
717     for(int i = buff.length(); i <= 4; i++) buff.insert(0, '0');
718     return buff.toString();
719   } // genSym
720 
721   /** GATE development environment configuration data (stored in gate.xml). */
722   private static OptionsMap userConfig = new OptionsMap();
723 
724   /**
725    * This map stores the init-time config data in case we need it later.
726    * GATE development environment configuration data (stored in gate.xml).
727    */
728   private static OptionsMap originalUserConfig = new OptionsMap();
729 
730   /** Name of the XML element for GATE development environment config data. */
731   private static String userConfigElement = "GATECONFIG";
732 
733   /**
734    * Gate the name of the XML element for GATE development environment
735    * config data.
736    */
737   public static String getUserConfigElement() { return userConfigElement; }
738 
739   /**
740    * Get the site config file (generally set during command-line processing
741    * or as a <TT>gate.config</TT> property).
742    * If the config is null, this method checks the <TT>gate.config</TT>
743    * property and uses it if non-null.
744    */
745   public static File getSiteConfigFile() {
746     if(siteConfigFile == null) {
747       String gateConfigProperty = System.getProperty(GATE_CONFIG_PROPERTY);
748       if(gateConfigProperty != null)
749         siteConfigFile = new File(gateConfigProperty);
750     }
751     return siteConfigFile;
752   } // getSiteConfigFile
753 
754   /** Set the site config file (e.g. during command-line processing). */
755   public static void setSiteConfigFile(File siteConfigFile) {
756     Gate.siteConfigFile = siteConfigFile;
757   } // setSiteConfigFile
758 
759   /** Site config file */
760   private static File siteConfigFile;
761 
762   /** Shorthand for local newline */
763   private static String nl = Strings.getNl();
764 
765   /** An empty config data file. */
766   private static String emptyConfigFile =
767     "<?xml version=\"1.0\"?>" + nl +
768     "<!-- " + GATE_DOT_XML + ": GATE configuration data -->" + nl +
769     "<GATE>" + nl +
770     "" + nl +
771     "<!-- NOTE: the next element may be overwritten by the GUI!!! -->" + nl +
772     "<" + userConfigElement + "/>" + nl +
773     "" + nl +
774     "</GATE>" + nl;
775 
776   /**
777    * Get an empty config file. <B>NOTE:</B> this method is intended only
778    * for use by the test suite.
779    */
780   public static String getEmptyConfigFile() { return emptyConfigFile; }
781 
782   /**
783    * Get the GATE development environment configuration data
784    * (initialised from <TT>gate.xml</TT>).
785    */
786   public static OptionsMap getUserConfig() { return userConfig; }
787 
788   /**
789    * Get the original, initialisation-time,
790    * GATE development environment configuration data
791    * (initialised from <TT>gate.xml</TT>).
792    */
793   public static OptionsMap getOriginalUserConfig() {
794     return originalUserConfig;
795   } // getOriginalUserConfig
796 
797   /**
798    * Update the GATE development environment configuration data in the
799    * user's <TT>gate.xml</TT> file (create one if it doesn't exist).
800    */
801   public static void writeUserConfig() throws GateException {
802     //update the values for knownPluginPath
803     String knownPluginPath = "";
804     Iterator pluginIter = knownPlugins.iterator();
805     while(pluginIter.hasNext()){
806       URL aPluginURL = (URL)pluginIter.next();
807       if(knownPluginPath.length() > 0) knownPluginPath += ";";
808       knownPluginPath += aPluginURL.toExternalForm();
809     }
810     getUserConfig().put(KNOWN_PLUGIN_PATH_KEY, knownPluginPath);
811     
812     //update the autoload plugin list
813     String loadPluginPath = "";
814     pluginIter = autoloadPlugins.iterator();
815     while(pluginIter.hasNext()){
816       URL aPluginURL = (URL)pluginIter.next();
817       if(loadPluginPath.length() > 0) loadPluginPath += ";";
818       loadPluginPath += aPluginURL.toExternalForm();
819     }
820     getUserConfig().put(LOAD_PLUGIN_PATH_KEY, loadPluginPath);
821     
822     // the user's config file
823     String configFileName = getUserConfigFileName();
824     File configFile = new File(configFileName);
825 
826     // create if not there, then update
827     try {
828       // if the file doesn't exist, create one with an empty GATECONFIG
829       if(! configFile.exists()) {
830         FileWriter writer = new FileWriter(configFile);
831         writer.write(emptyConfigFile);
832         writer.close();
833       }
834 
835       // update the config element of the file
836       Files.updateXmlElement(
837         new File(configFileName), userConfigElement, userConfig
838       );
839 
840     } catch(IOException e) {
841       throw new GateException(
842         "problem writing user " + GATE_DOT_XML + ": " + nl + e.toString()
843       );
844     }
845   } // writeUserConfig
846 
847   /**
848    * Get the name of the user's <TT>gate.xml</TT> config file (this
849    * doesn't guarantee that file exists!).
850    */
851   public static String getUserConfigFileName() {
852     String filePrefix = "";
853     if(runningOnUnix()) filePrefix = ".";
854 
855     String userConfigName =
856       System.getProperty("user.home") + Strings.getFileSep() +
857       filePrefix + GATE_DOT_XML;
858     return userConfigName;
859   } // getUserConfigFileName
860 
861   /**
862    * Get the name of the user's <TT>gate.ser</TT> session state file (this
863    * doesn't guarantee that file exists!).
864    */
865   public static String getUserSessionFileName() {
866     String filePrefix = "";
867     if(runningOnUnix()) filePrefix = ".";
868 
869     String userSessionName =
870       System.getProperty("user.home") + Strings.getFileSep() +
871       filePrefix + GATE_DOT_SER;
872     return userSessionName;
873   } // getUserSessionFileName
874 
875   /**
876    * This method tries to guess if we are on a UNIX system. It does this
877    * by checking the value of <TT>System.getProperty("file.separator")</TT>;
878    * if this is "/" it concludes we are on UNIX. <B>This is obviously not
879    * a very good idea in the general case, so nothing much should be made
880    * to depend on this method (e.g. just naming of config file
881    * <TT>.gate.xml</TT> as opposed to <TT>gate.xml</TT>)</B>.
882    */
883   public static boolean runningOnUnix() {
884     return Strings.getFileSep().equals("/");
885   } // runningOnUnix
886 
887   /**
888    * Returns the list of CREOLE directories the system knows about (either 
889    * pre-installed plugins in the plugins directory or CREOLE directories that
890    * have previously been loaded manually).
891    * @return a {@link List} of {@link URL}s.
892    */
893   public static List getKnownPlugins(){
894     return knownPlugins;
895   }
896   
897   public static void addKnownPlugin(URL pluginURL){
898     if(knownPlugins.contains(pluginURL)) return;
899     knownPlugins.add(pluginURL);
900   }
901 
902   /**
903    * Returns the list of CREOLE directories the system loads automatically at
904    * start-up.
905    * @return a {@link List} of {@link URL}s.
906    */
907   public static List getAutoloadPlugins(){
908     return autoloadPlugins;
909   }
910   
911   public static void addAutoloadPlugin(URL pluginUrl){
912     if(autoloadPlugins.contains(pluginUrl))return;
913     //make sure it's known
914     addKnownPlugin(pluginUrl);
915     //add it to autoload list
916     autoloadPlugins.add(pluginUrl);
917   }
918   
919   /**
920    * Gets the information about a known directory.
921    * @param directory the URL for the directory in question.
922    * @return a {@link DirectoryInfo} value.
923    */
924   public static DirectoryInfo getDirectoryInfo(URL directory){
925     if(!knownPlugins.contains(directory)) return null;
926     DirectoryInfo dInfo = (DirectoryInfo)pluginData.get(directory);
927     if(dInfo == null){
928       dInfo = new DirectoryInfo(directory);
929       pluginData.put(directory, dInfo);
930     }    
931     return dInfo;
932   }
933   
934   /**
935    * Tells the system to &quot;forget&quot; about one previously known 
936    * directory. If the specified directory was loaded, it will be unloaded as 
937    * well - i.e. all the metadata relating to resources defined by this 
938    * directory will be removed from memory.
939    * @param directory
940    */
941   public static void removeKnownDirectory(URL directory){
942     DirectoryInfo dInfo = (DirectoryInfo)pluginData.get(directory);
943     if(dInfo != null){
944       creoleRegister.removeDirectory(directory);
945       knownPlugins.remove(directory);
946       pluginData.remove(directory);
947     }
948   }  
949   
950   /**
951    * Stores information about the contents of a CREOLE directory.
952    */
953   public static class DirectoryInfo{
954     public DirectoryInfo(URL url){
955       this.url = url;
956       valid = true;
957       resourceInfoList = new ArrayList();
958       //this may invalidate it if something goes wrong
959       parseCreole();
960     }
961     
962     /**
963      * Performs a shallow parse of the creole.xml file to get the information 
964      * about the resources contained.
965      */
966     protected void parseCreole(){
967       SAXBuilder builder = new SAXBuilder(false);
968       try{
969         if(!url.getPath().endsWith("/")) 
970           url = new URL(url.getProtocol(), url.getHost(),
971                   url.getPort(), url.getPath() + "/");
972         URL creoleFileURL = new URL(url, "creole.xml");
973         org.jdom.Document creoleDoc = builder.build(creoleFileURL);
974         List jobsList = new ArrayList();
975         jobsList.add(creoleDoc.getRootElement());
976         while(!jobsList.isEmpty()){
977           Element currentElem = (Element)jobsList.remove(0);
978           if(currentElem.getName().equalsIgnoreCase("RESOURCE")){
979             //we don't go deeper than resources so no recursion here
980             String resName = currentElem.getChildTextTrim("NAME");
981             String resClass = currentElem.getChildTextTrim("CLASS");
982             String resComment = currentElem.getChildTextTrim("COMMENT");
983             //create the handler
984             ResourceInfo rHandler = new ResourceInfo(resName, resClass, 
985                     resComment);
986             resourceInfoList.add(rHandler);
987           }else{
988             //this is some higher level element -> simulate recursion
989             //we want Depth-first-search so we need to add at the beginning
990             List newJobsList = new ArrayList(currentElem.getChildren());
991             newJobsList.addAll(jobsList);
992             jobsList = newJobsList;
993           }
994         }
995       }catch(IOException ioe){
996         valid = false;
997         ioe.printStackTrace();
998       }catch(JDOMException jde){
999         valid = false;
1000        jde.printStackTrace();
1001      }
1002    }
1003    
1004    /**
1005     * @return Returns the resourceInfoList.
1006     */
1007    public List getResourceInfoList(){
1008      return resourceInfoList;
1009    }
1010    /**
1011     * @return Returns the url.
1012     */
1013    public URL getUrl(){
1014      return url;
1015    }
1016    /**
1017     * @return Returns the valid.
1018     */
1019    public boolean isValid(){
1020      return valid;
1021    }
1022    /**
1023     * The URL for the CREOLE directory. 
1024     */
1025    protected URL url;
1026    
1027    /**
1028     * Is the directory valid (i.e. is the location reachable and the 
1029     * creole.xml file parsable).
1030     */
1031    protected boolean valid;
1032    
1033    /**
1034     * The list of {@link Gate.ResourceInfo} objects.
1035     */
1036    protected List resourceInfoList;
1037  }
1038  
1039  /**
1040   * Stores information about a resource defined by a CREOLE directory. The 
1041   * resource might not have been loaded in the system so not all information
1042   * normally provided by the {@link ResourceData} class is available. This is 
1043   * what makes this class different from {@link ResourceData}. 
1044   */
1045  public static class ResourceInfo{
1046    public ResourceInfo(String name, String className, String comment){
1047      this.resourceClassName = className;
1048      this.resourceName = name;
1049      this.resourceComment = comment;
1050    }
1051    
1052    /**
1053     * @return Returns the resourceClassName.
1054     */
1055    public String getResourceClassName(){
1056      return resourceClassName;
1057    }
1058    /**
1059     * @return Returns the resourceComment.
1060     */
1061    public String getResourceComment(){
1062      return resourceComment;
1063    }
1064    /**
1065     * @return Returns the resourceName.
1066     */
1067    public String getResourceName(){
1068      return resourceName;
1069    }
1070    /**
1071     * The class for the resource.
1072     */
1073    protected String resourceClassName;
1074    
1075    /**
1076     * The resource name.
1077     */
1078    protected String resourceName;
1079    
1080    /**
1081     * The comment for the resource.
1082     */
1083    protected String resourceComment;
1084  }
1085  
1086  /**
1087   * The list of plugins (aka CREOLE directories) the system knows about.
1088   * This list contains URL objects.
1089   */
1090  protected static List knownPlugins;
1091  
1092  /**
1093   * The list of plugins (aka CREOLE directories) the system loads automatically
1094   * at start-up.
1095   * This list contains URL objects.
1096   */
1097  protected static List autoloadPlugins;
1098  
1099  
1100  /**
1101   * Map from URL of directory to {@link DirectoryInfo}.
1102   */
1103  protected static Map pluginData;
1104  
1105  
1106  
1107  /** Flag for SLUG GUI start instead of standart GATE GUI. */
1108  private static boolean slugGui = false;
1109
1110  /** Should we start SLUG GUI. */
1111  public static boolean isSlugGui() { return slugGui; }
1112
1113  /** Tell GATE whether to start SLUG GUI. */
1114  public static void setSlugGui(boolean b) { slugGui = b; }
1115
1116  /** Flag for Jape Debugger integration. */
1117  private static boolean enableJapeDebug = false;
1118
1119  /** Should we enable Jape Debugger. */
1120  public static boolean isEnableJapeDebug() { return enableJapeDebug; }
1121
1122  /** Tell GATE whether to enable Jape Debugger. */
1123  public static void setEnableJapeDebug(boolean b) { enableJapeDebug = b; }
1124
1125} // class Gate
1126