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.76 2004/12/06 12:19:31 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      //init local paths
91      initLocalPaths();
92      
93      // register the URL handler  for the "gate://" URLs
94      System.setProperty(
95        "java.protocol.handler.pkgs",
96        System.getProperty("java.protocol.handler.pkgs")
97          + "|" + "gate.util.protocols"
98      );
99  
100     //System.setProperty("javax.xml.parsers.SAXParserFactory",
101       //                       "org.apache.xerces.jaxp.SAXParserFactoryImpl");
102 
103     //initialise the symbols generator
104     lastSym = 0;
105 
106     // create class loader and creole register if they're null
107     if(classLoader == null)
108       classLoader = new GateClassLoader(Gate.class.getClassLoader());
109     if(creoleRegister == null)
110       creoleRegister = new CreoleRegisterImpl();
111     if(knownPlugins == null) knownPlugins = new ArrayList();
112     if(autoloadPlugins == null) autoloadPlugins = new ArrayList();
113     if(pluginData == null) pluginData = new HashMap();
114     // init the creole register
115     initCreoleRegister();
116     // init the data store register
117     initDataStoreRegister();
118     // read gate.xml files; this must come before creole register
119     // initialisation in order for the CREOLE-DIR elements to have and effect
120     initConfigData();
121     
122     initCreoleRepositories();
123     // the creoleRegister acts as a proxy for datastore related events
124     dataStoreRegister.addCreoleListener(creoleRegister);
125 
126     // some of the events are actually fired by the {@link gate.Factory}
127     Factory.addCreoleListener(creoleRegister);
128 
129     // check we have a useable JDK
130     if(System.getProperty("java.version").compareTo(MIN_JDK_VERSION) < 0) {
131       throw new GateException(
132         "GATE requires JDK " + MIN_JDK_VERSION + " or newer"
133       );
134     }
135 
136     //register Lucene as a IR search engine
137     try{
138       registerIREngine("gate.creole.ir.lucene.LuceneIREngine");
139     }catch(ClassNotFoundException cnfe){
140       throw new GateRuntimeException(cnfe);
141     }
142   } // init()
143   
144   /**
145    * Initialises the paths to local files of interest like the GATE home, 
146    * the installed plugins home and site and user configuration files.
147    */
148   protected static void initLocalPaths(){
149     //GATE Home
150     if(gateHome == null){
151       String gateHomeStr = System.getProperty(GATE_HOME_PROPERTY_NAME);
152       if(gateHomeStr != null && gateHomeStr.length() > 0){
153         gateHome = new File(gateHomeStr);
154       }
155       //if failed, try to guess
156       if(gateHome == null || !gateHome.exists()){
157         System.err.println("GATE home system property (\"" + 
158                 GATE_HOME_PROPERTY_NAME + "\") not set.\nAttempting to guess...");
159         URL gateURL = Thread.currentThread().getContextClassLoader().
160           getResource("gate/Gate.class");
161         try{
162           if(gateURL.getProtocol().equals("jar")){
163             //running from gate.jar
164             String gateURLStr = gateURL.getFile();
165               File gateJarFile = new File(
166                       new URL(gateURLStr.substring(0, gateURLStr.indexOf('!'))).
167                       getFile());
168               gateHome = gateJarFile.getParentFile().getParentFile();
169           }else if(gateURL.getProtocol().equals("file")){
170             //running from classes directory
171             File gateClassFile = new File(gateURL.getFile());
172             gateHome = gateClassFile.getParentFile().
173               getParentFile().getParentFile();
174           }
175           System.err.println("Using \"" + 
176                   gateHome.getCanonicalPath() + 
177                   "\" as GATE Home.\nIf this is not correct please set it manually" + 
178                   " using the -D" + GATE_HOME_PROPERTY_NAME + 
179                   " option in your start-up script");
180         }catch(Throwable thr){
181           throw new GateRuntimeException(
182                   "Cannot guess GATE Home. Pease set it manually!", thr);
183         }          
184       }
185     }
186     System.out.println("Using " + gateHome.toString() + " as GATE home");
187     
188     //Plugins home
189     if(pluginsHome == null){
190       String pluginsHomeStr = System.getProperty(PLUGINS_HOME_PROPERTY_NAME);
191       if(pluginsHomeStr != null && pluginsHomeStr.length() > 0){
192         File homeFile = new File(pluginsHomeStr);
193         if(homeFile.exists() && homeFile.isDirectory()){
194           pluginsHome = homeFile;
195         }
196       }
197       //if not set, use the GATE Home as a base directory
198       if(pluginsHome == null){
199         File homeFile = new File(gateHome, PLUGINS);
200         if(homeFile.exists() && homeFile.isDirectory()){
201           pluginsHome = homeFile;
202         }
203       }
204       //if still not set, throw exception
205       if(pluginsHome == null){
206         throw new GateRuntimeException(
207                 "Could not infer installed plug-ins home!\n" + 
208                 "Please set it manually using the -D" + 
209                 PLUGINS_HOME_PROPERTY_NAME + " option in your start-up script.");
210       }
211     }
212     System.out.println("Using " + pluginsHome.toString() + 
213             " as installed plug-ins directory.");
214     
215     //site config
216     if(siteConfigFile == null){
217       String siteConfigStr = System.getProperty(SITE_CONFIG_PROPERTY_NAME);
218       if(siteConfigStr != null && siteConfigStr.length() > 0){
219         File configFile = new File(siteConfigStr);
220         if(configFile.exists()) siteConfigFile = configFile;
221       }
222       //if not set, use GATE home as base directory
223       if(siteConfigFile == null){
224         File configFile = new File(gateHome, GATE_DOT_XML);
225         if(configFile.exists()) siteConfigFile = configFile;
226       }
227       //if still not set, throw exception
228       if(siteConfigFile == null){
229         throw new GateRuntimeException(
230             "Could not locate the site configuration file!\n" + 
231             "Please create it at " + 
232             new File(gateHome, GATE_DOT_XML).toString() +
233             " or point to an existing one using the -D" + 
234             SITE_CONFIG_PROPERTY_NAME + " option in your start-up script!");
235       }
236     }
237     System.out.println("Using " + siteConfigFile.toString() + 
238     " as site configuration file.");
239     
240     //user config
241     if(userConfigFile == null){
242       String userConfigStr = System.getProperty(USER_CONFIG_PROPERTY_NAME);
243       if(userConfigStr != null && userConfigStr.length() > 0){
244         File configFile = new File(userConfigStr);
245         if(configFile.exists()) userConfigFile = configFile;
246       }
247       //if still not set, use the user's home as a base directory
248       if(userConfigFile == null){
249         userConfigFile = new File(
250             System.getProperty("user.home") + 
251             Strings.getFileSep() + 
252             (runningOnUnix() ? "." : "") +
253             GATE_DOT_XML);
254       }
255       System.out.println("Using " + userConfigFile + " as user configuration file");
256     }
257   }
258   
259   /**
260    * Loads the CREOLE repositories (aka plugins) that the user has selected for 
261    * automatic loading.
262    * Loads the information about known plugins in memory.
263    */
264   protected static void initCreoleRepositories(){
265     //the logic is:
266     //get the list of know plugins from gate.xml
267     //add all the installed plugins
268     //get the list of loadable plugins 
269     //or use ANNIE if value not set
270     //load loadable plugins
271     
272     //process the known plugins list
273     String knownPluginsPath = (String)getUserConfig().get(KNOWN_PLUGIN_PATH_KEY);
274     if(knownPluginsPath != null && knownPluginsPath.length() > 0){
275       StringTokenizer strTok = new StringTokenizer(knownPluginsPath, ";", false);
276       while(strTok.hasMoreTokens()){
277         String aKnownPluginPath = strTok.nextToken();
278         try{
279           URL aPluginURL = new URL(aKnownPluginPath);
280           addKnownPlugin(aPluginURL);
281         }catch(MalformedURLException mue){
282           Err.prln("Plugin error: " + aKnownPluginPath + " is an invalid URL!");
283         }
284       }
285     }
286     //add all the installed plugins
287     File pluginsHome = new File(System.getProperty(GATE_HOME_PROPERTY_NAME), 
288             "plugins");
289     File[] dirs = pluginsHome.listFiles();
290     for(int i = 0; i < dirs.length; i++){
291       File creoleFile = new File(dirs[i], "creole.xml");
292       if(creoleFile.exists()){
293         try{
294           URL pluginURL = dirs[i].toURL();
295           addKnownPlugin(pluginURL);
296         }catch(MalformedURLException mue){
297           //this shoulod never happen
298           throw new GateRuntimeException(mue);
299         }
300       }
301     }
302 
303     //process the autoload plugins
304     String pluginPath = getUserConfig().getString(AUTOLOAD_PLUGIN_PATH_KEY);
305     //can be overridden by system property
306     String prop = System.getProperty(AUTOLOAD_PLUGIN_PATH_PROPERTY_NAME);
307     if(prop != null && prop.length() > 0) pluginPath = prop;
308     
309     if(pluginPath == null || pluginPath.length() == 0){
310       //value not set -> use the default
311       try{
312         pluginPath = new File(pluginsHome, "ANNIE/").toURL().toString();
313         getUserConfig().put(AUTOLOAD_PLUGIN_PATH_KEY, pluginPath);
314       }catch(MalformedURLException mue){
315         throw new GateRuntimeException(mue);
316       }
317     }
318     
319     //load all loadable plugins
320     StringTokenizer strTok = new StringTokenizer(pluginPath, 
321             ";", false);
322     while(strTok.hasMoreTokens()){
323       String aDir = strTok.nextToken();
324       try{
325         URL aPluginURL = new URL(aDir);
326         addAutoloadPlugin(aPluginURL);
327       }catch(MalformedURLException mue){
328         System.err.println("Cannot load " + aDir + " CREOLE repository.");
329         mue.printStackTrace();
330       }
331       try{
332         Iterator loadPluginsIter = getAutoloadPlugins().iterator();
333         while(loadPluginsIter.hasNext()){  
334           getCreoleRegister().registerDirectories((URL)loadPluginsIter.next());
335         }
336       }catch(GateException ge){
337         System.err.println("Cannot load " + aDir + " CREOLE repository.");
338         ge.printStackTrace();
339       }
340     }
341   }
342 
343   /** Initialise the CREOLE register. */
344   public static void initCreoleRegister() throws GateException {
345 
346     // register the builtin CREOLE directories
347     for(int i=0; i<builtinCreoleDirectoryUrls.length; i++)
348       try {
349         creoleRegister.addDirectory(
350           new URL(builtinCreoleDirectoryUrls[i])
351         );
352       } catch(MalformedURLException e) {
353         throw new GateException(e);
354       }
355 
356 /*
357 We'll have to think about this. Right now it points to the creole inside the
358 jar/classpath so it's the same as registerBuiltins
359 */
360 //    // add the GATE base URL creole directory
361 //    creoleRegister.addDirectory(Gate.getUrl("creole/"));
362 //    creoleRegister.registerDirectories();
363 
364     // register the resources that are actually in gate.jar
365     creoleRegister.registerBuiltins();
366   } // initCreoleRegister
367 
368   /** Initialise the DataStore register. */
369   public static void initDataStoreRegister() {
370     dataStoreRegister = new DataStoreRegister();
371   } // initDataStoreRegister()
372 
373   /**
374    * Reads config data (<TT>gate.xml</TT> files). There are three
375    * sorts of these files:
376    * <UL>
377    * <LI>
378    * The builtin file from GATE's resources - this is read first.
379    * <LI>
380    * A site-wide init file given as a command-line argument or as a
381    * <TT>gate.config</TT> property - this is read second.
382    * <LI>
383    * The user's file from their home directory - this is read last.
384    * </UL>
385    * Settings from files read after some settings have already been
386    * made will simply overwrite the previous settings.
387    */
388   public static void initConfigData() throws GateException {
389     ConfigDataProcessor configProcessor = new ConfigDataProcessor();
390     //parse the site configuration file
391     URL configURL;
392     try{
393       configURL = siteConfigFile.toURL();
394     }catch(MalformedURLException mue){
395       //this should never happen
396       throw new GateRuntimeException(mue);
397     }
398     try {
399       InputStream configStream = new FileInputStream(siteConfigFile);
400       configProcessor.parseConfigFile(configStream, configURL);
401     } catch(IOException e) {
402       throw new GateException(
403         "Couldn't open site configuration file: " + configURL + " " + e
404       );
405     }
406     
407     //parse the user configuration data if present
408     if(userConfigFile != null && userConfigFile.exists()){
409       try{
410         configURL = userConfigFile.toURL();
411       }catch(MalformedURLException mue){
412         //this should never happen
413         throw new GateRuntimeException(mue);
414       }
415       try {
416         InputStream configStream = new FileInputStream(userConfigFile);
417         configProcessor.parseConfigFile(configStream, configURL);
418       } catch(IOException e) {
419         throw new GateException(
420           "Couldn't open user configuration file: " + configURL + " " + e
421         );
422       }    
423     }
424 
425     // remember the init-time config options
426     originalUserConfig.putAll(userConfig);
427 
428     if(DEBUG) {
429       Out.prln(
430         "user config loaded; DBCONFIG=" + DataStoreRegister.getConfigData()
431       );
432     }
433   } // initConfigData()
434 
435   /**
436    * Attempts to guess the Unicode font for the platform.
437    */
438   public static String guessUnicodeFont(){
439     //guess the Unicode font for the platform
440     String[] fontNames = java.awt.GraphicsEnvironment.
441                          getLocalGraphicsEnvironment().
442                          getAvailableFontFamilyNames();
443     String unicodeFontName = null;
444     for(int i = 0; i < fontNames.length; i++){
445       if(fontNames[i].equalsIgnoreCase("Arial Unicode MS")){
446         unicodeFontName = fontNames[i];
447         break;
448       }
449       if(fontNames[i].toLowerCase().indexOf("unicode") != -1){
450         unicodeFontName = fontNames[i];
451       }
452     }//for(int i = 0; i < fontNames.length; i++)
453     return unicodeFontName;
454   }
455 
456   /** Get a URL that points to either an HTTP server or a file system
457     * that contains GATE files (such as test cases). The following locations
458     * are tried in sequence:
459     * <UL>
460     * <LI>
461     * <TT>http://derwent.dcs.shef.ac.uk/gate.ac.uk/</TT>, a Sheffield-internal
462     * development server (the gate.ac.uk affix is a copy of the file system
463     * present on GATE's main public server - see next item);
464     * <LI>
465     * <TT>http://gate.ac.uk/</TT>, GATE's main public server;
466     * <LI>
467     * <TT>http://localhost/gate.ac.uk/</TT>, a Web server running on the
468     * local machine;
469     * <LI>
470     * the local file system where the binaries for the
471     * current invocation of GATE are stored.
472     * </UL>
473     * In each case we assume that a Web server will be running on port 80,
474     * and that if we can open a socket to that port then the server is
475     * running. (This is a bit of a strong assumption, but this URL is used
476     * largely by the test suite, so we're not betting anything too critical
477     * on it.)
478     * <P>
479     * Note that the value returned will only be calculated when the existing
480     * value recorded by this class is null (which will be the case when
481     * neither setUrlBase nor getUrlBase have been called, or if
482     * setUrlBase(null) has been called).
483     */
484   public static URL getUrl() throws GateException {
485     if(urlBase != null) return urlBase;
486 
487     try {
488 
489        // if we're assuming a net connection, try network servers
490       if(isNetConnected()) {
491         if(
492           tryNetServer("gate-internal.dcs.shef.ac.uk", 80, "/") ||
493    //       tryNetServer("derwent.dcs.shef.ac.uk", 80, "/gate.ac.uk/") ||
494           tryNetServer("gate.ac.uk", 80, "/")
495         ) {
496             if(DEBUG) Out.prln("getUrl() returned " + urlBase);
497             return urlBase;
498         }
499       } // if isNetConnected() ...
500 
501       // no network servers; try for a local host web server.
502       // we use InetAddress to get host name instead of using "localhost" coz
503       // badly configured Windoze IP sometimes doesn't resolve the latter
504       if(
505         isLocalWebServer() &&
506         tryNetServer(
507           InetAddress.getLocalHost().getHostName(), 80, "/gate.ac.uk/"
508         )
509       ) {
510         if(DEBUG) Out.prln("getUrlBase() returned " + urlBase);
511         return urlBase;
512       }
513 
514       // try the local file system
515       tryFileSystem();
516 
517     } catch(MalformedURLException e) {
518       throw new GateException("Bad URL, getUrlBase(): " + urlBase + ": " + e);
519     } catch(UnknownHostException e) {
520       throw new GateException("No host, getUrlBase(): " + urlBase + ": " + e);
521     }
522 
523     // return value will be based on the file system, or null
524     if(DEBUG) Out.prln("getUrlBase() returned " + urlBase);
525     return urlBase;
526   } // getUrl()
527 
528   /** Get a URL that points to either an HTTP server or a file system
529     * that contains GATE files (such as test cases).
530     * Calls <TT>getUrl()</TT> then adds the <TT>path</TT> parameter to
531     * the result.
532     * @param path a path to add to the base URL.
533     * @see #getUrl()
534     */
535   public static URL getUrl(String path) throws GateException {
536     getUrl();
537     if(urlBase == null)
538       return null;
539 
540     URL newUrl = null;
541     try {
542       newUrl = new URL(urlBase, path);
543     } catch(MalformedURLException e) {
544       throw new GateException("Bad URL, getUrl( " + path + "): " + e);
545     }
546 
547     if(DEBUG) Out.prln("getUrl(" + path + ") returned " + newUrl);
548     return newUrl;
549   } // getUrl(path)
550 
551   /** Flag controlling whether we should try to access the net, e.g. when
552     * setting up a base URL.
553     */
554   private static boolean netConnected = true;
555 
556   private static int lastSym;
557 
558   /**
559    * A list of names of classes that implement {@link gate.creole.ir.IREngine}
560    * that will be used as information retrieval engines.
561    */
562   private static Set registeredIREngines = new HashSet();
563 
564   /**
565    * Registers a new IR engine. The class named should implement
566    * {@link gate.creole.ir.IREngine}.
567    * @param className the fully qualified name of the class to be registered
568    * @throws GateException if the class does not implement the
569    * {@link gate.creole.ir.IREngine} interface.
570    * @throws ClassNotFoundException if the named class cannot be found.
571    */
572   public static void registerIREngine(String className)
573     throws GateException, ClassNotFoundException{
574     Class aClass = Class.forName(className);
575     if(gate.creole.ir.IREngine.class.isAssignableFrom(aClass)){
576       registeredIREngines.add(className);
577     }else{
578       throw new GateException(className + " does not implement the " +
579                               gate.creole.ir.IREngine.class.getName() +
580                               " interface!");
581     }
582   }
583 
584   /**
585    * Unregisters a previously registered IR engine.
586    * @param className the name of the class to be removed from the list of
587    * registered IR engines.
588    * @return true if the class was found and removed.
589    */
590   public static boolean unregisterIREngine(String className){
591     return registeredIREngines.remove(className);
592   }
593 
594   /**
595    * Gets the set of registered IR engines.
596    * @return an unmodifiable {@link java.util.Set} value.
597    */
598   public static Set getRegisteredIREngines(){
599     return Collections.unmodifiableSet(registeredIREngines);
600   }
601   
602   /**
603    * Gets the GATE home location.
604    * @return a File value.
605    */
606   public static File getGateHome(){
607     return gateHome;
608   }
609 
610   /** Should we assume we're connected to the net? */
611   public static boolean isNetConnected() { return netConnected; }
612 
613   /**
614    * Tell GATE whether to assume we're connected to the net. Has to be
615    * called <B>before</B> {@link #init()}.
616    */
617   public static void setNetConnected(boolean b) { netConnected = b; }
618 
619   /**
620    * Flag controlling whether we should try to access a web server on
621    * localhost, e.g. when setting up a base URL. Has to be
622    * called <B>before</B> {@link #init()}.
623    */
624   private static boolean localWebServer = true;
625 
626   /** Should we assume there's a local web server? */
627   public static boolean isLocalWebServer() { return localWebServer; }
628 
629   /** Tell GATE whether to assume there's a local web server. */
630   public static void setLocalWebServer(boolean b) { localWebServer = b; }
631 
632   /** Try to contact a network server. When sucessfull sets urlBase to an HTTP
633     * URL for the server.
634     * @param hostName the name of the host to try and connect to
635     * @param serverPort the port to try and connect to
636     * @param path a path to append to the URL when we make a successfull
637     * connection. E.g. for host xyz, port 80, path /thing, the resultant URL
638     * would be <TT>http://xyz:80/thing</TT>.
639     */
640   public static boolean tryNetServer(
641     String hostName, int serverPort, String path
642   ) throws MalformedURLException {
643     Socket socket = null;
644     if(DEBUG)
645       Out.prln(
646         "tryNetServer(hostName=" + hostName + ", serverPort=" + serverPort +
647         ", path=" + path +")"
648       );
649 
650     // is the host listening at the port?
651     try{
652       URL url = new URL("http://" + hostName + ":" + serverPort + "/");
653       URLConnection uConn =  url.openConnection();
654       HttpURLConnection huConn = null;
655       if(uConn instanceof HttpURLConnection)
656         huConn = (HttpURLConnection)uConn;
657       if(huConn.getResponseCode() == -1) return false;
658     } catch (IOException e){
659       return false;
660     }
661 
662 //    if(socket != null) {
663       urlBase = new URL("http", hostName, serverPort, path);
664       return true;
665 //    }
666 
667 //    return false;
668   } // tryNetServer()
669 
670   /** Try to find GATE files in the local file system */
671   protected static boolean tryFileSystem() throws MalformedURLException {
672     String urlBaseName = locateGateFiles();
673     if(DEBUG) Out.prln("tryFileSystem: " + urlBaseName);
674 
675     urlBase = new URL(urlBaseName + "gate/resources/gate.ac.uk/");
676     return urlBase == null;
677   } // tryFileSystem()
678 
679   /**
680    * Find the location of the GATE binaries (and resources) in the
681    * local file system.
682    */
683   public static String locateGateFiles() {
684     String aGateResourceName = "gate/resources/creole/creole.xml";
685     URL resourcesUrl = Gate.getClassLoader().getResource(aGateResourceName);
686 
687     StringBuffer basePath = new StringBuffer(resourcesUrl.toExternalForm());
688     String urlBaseName =
689       basePath.substring(0, basePath.length() - aGateResourceName.length());
690 
691     return urlBaseName;
692   } // locateGateFiles
693 
694   /**
695    * Checks whether a particular class is a Gate defined type
696    */
697   public static boolean isGateType(String classname){
698     boolean res = getCreoleRegister().containsKey(classname);
699     if(!res){
700       try{
701         Class aClass = Class.forName(classname);
702         res = Resource.class.isAssignableFrom(aClass) ||
703               Controller.class.isAssignableFrom(aClass) ||
704               DataStore.class.isAssignableFrom(aClass);
705       }catch(ClassNotFoundException cnfe){
706         return false;
707       }
708     }
709     return res;
710   }
711 
712   /** Returns the value for the HIDDEN attribute of a feature map */
713   static public boolean getHiddenAttribute(FeatureMap fm){
714     if(fm == null) return false;
715     Object value = fm.get("gate.HIDDEN");
716     return value != null &&
717            value instanceof String &&
718            ((String)value).equals("true");
719   }
720 
721   /** Sets the value for the HIDDEN attribute of a feature map */
722   static public void setHiddenAttribute(FeatureMap fm, boolean hidden){
723     if(hidden){
724       fm.put("gate.HIDDEN", "true");
725     }else{
726       fm.remove("gate.HIDDEN");
727     }
728   }
729 
730 
731   /** Registers a {@link gate.event.CreoleListener} with the Gate system
732     */
733   public static synchronized void addCreoleListener(CreoleListener l){
734     creoleRegister.addCreoleListener(l);
735   } // addCreoleListener
736 
737   /** Set the URL base for GATE files, e.g. <TT>http://gate.ac.uk/</TT>. */
738   public static void setUrlBase(URL urlBase) { Gate.urlBase = urlBase; }
739 
740   /** The URL base for GATE files, e.g. <TT>http://gate.ac.uk/</TT>. */
741   private static URL urlBase = null;
742 
743   /** Class loader used e.g. for loading CREOLE modules, of compiling
744     * JAPE rule RHSs.
745     */
746   private static GateClassLoader classLoader = null;
747 
748   /** Get the GATE class loader. */
749   public static GateClassLoader getClassLoader() { return classLoader; }
750 
751   /** The CREOLE register. */
752   private static CreoleRegister creoleRegister = null;
753 
754   /** Get the CREOLE register. */
755   public static CreoleRegister getCreoleRegister() { return creoleRegister; }
756 
757   /** The DataStore register */
758   private static DataStoreRegister dataStoreRegister = null;
759 
760   /**
761    * The current executable under execution.
762    */
763   private static gate.Executable currentExecutable;
764 
765   /** Get the DataStore register. */
766   public static DataStoreRegister getDataStoreRegister() {
767     return dataStoreRegister;
768   } // getDataStoreRegister
769 
770   /**
771    * Sets the {@link Executable} currently under execution.
772    * At a given time there can be only one executable set. After the executable
773    * has finished its execution this value should be set back to null.
774    * An attempt to set the executable while this value is not null will result
775    * in the method call waiting until the old executable is set to null.
776    */
777   public synchronized static void setExecutable(gate.Executable executable) {
778     if(executable == null) currentExecutable = executable;
779     else{
780       while(getExecutable() != null){
781         try{
782           Thread.sleep(200);
783         }catch(InterruptedException ie){
784           throw new LuckyException(ie.toString());
785         }
786       }
787       currentExecutable = executable;
788     }
789   } // setExecutable
790 
791   /**
792    * Returns the curently set executable.
793    * @see #setExecutable(gate.Executable)
794    */
795   public synchronized static gate.Executable getExecutable() {
796     return currentExecutable;
797   } // getExecutable
798 
799 
800   /**
801    * Returns a new unique string
802    */
803   public synchronized static String genSym() {
804     StringBuffer buff = new StringBuffer(Integer.toHexString(lastSym++).
805                                          toUpperCase());
806     for(int i = buff.length(); i <= 4; i++) buff.insert(0, '0');
807     return buff.toString();
808   } // genSym
809 
810   /** GATE development environment configuration data (stored in gate.xml). */
811   private static OptionsMap userConfig = new OptionsMap();
812 
813   /**
814    * This map stores the init-time config data in case we need it later.
815    * GATE development environment configuration data (stored in gate.xml).
816    */
817   private static OptionsMap originalUserConfig = new OptionsMap();
818 
819   /** Name of the XML element for GATE development environment config data. */
820   private static String userConfigElement = "GATECONFIG";
821 
822   /**
823    * Gate the name of the XML element for GATE development environment
824    * config data.
825    */
826   public static String getUserConfigElement() { return userConfigElement; }
827 
828   /**
829    * Get the site config file (generally set during command-line processing
830    * or as a <TT>gate.config</TT> property).
831    * If the config is null, this method checks the <TT>gate.config</TT>
832    * property and uses it if non-null.
833    */
834   public static File getSiteConfigFile() {
835     if(siteConfigFile == null) {
836       String gateConfigProperty = System.getProperty(GATE_CONFIG_PROPERTY);
837       if(gateConfigProperty != null)
838         siteConfigFile = new File(gateConfigProperty);
839     }
840     return siteConfigFile;
841   } // getSiteConfigFile
842 
843   /** Set the site config file (e.g. during command-line processing). */
844   public static void setSiteConfigFile(File siteConfigFile) {
845     Gate.siteConfigFile = siteConfigFile;
846   } // setSiteConfigFile
847 
848   /** Shorthand for local newline */
849   private static String nl = Strings.getNl();
850 
851   /** An empty config data file. */
852   private static String emptyConfigFile =
853     "<?xml version=\"1.0\"?>" + nl +
854     "<!-- " + GATE_DOT_XML + ": GATE configuration data -->" + nl +
855     "<GATE>" + nl +
856     "" + nl +
857     "<!-- NOTE: the next element may be overwritten by the GUI!!! -->" + nl +
858     "<" + userConfigElement + "/>" + nl +
859     "" + nl +
860     "</GATE>" + nl;
861 
862   /**
863    * Get an empty config file. <B>NOTE:</B> this method is intended only
864    * for use by the test suite.
865    */
866   public static String getEmptyConfigFile() { return emptyConfigFile; }
867 
868   /**
869    * Get the GATE development environment configuration data
870    * (initialised from <TT>gate.xml</TT>).
871    */
872   public static OptionsMap getUserConfig() { return userConfig; }
873 
874   /**
875    * Get the original, initialisation-time,
876    * GATE development environment configuration data
877    * (initialised from <TT>gate.xml</TT>).
878    */
879   public static OptionsMap getOriginalUserConfig() {
880     return originalUserConfig;
881   } // getOriginalUserConfig
882 
883   /**
884    * Update the GATE development environment configuration data in the
885    * user's <TT>gate.xml</TT> file (create one if it doesn't exist).
886    */
887   public static void writeUserConfig() throws GateException {
888     String pluginsHomeStr;
889     try{
890       pluginsHomeStr = pluginsHome.getCanonicalPath();
891     }catch(IOException ioe){
892       throw new GateRuntimeException("Problem while locating the plug-ins home!",
893               ioe);
894     }
895     //update the values for knownPluginPath
896     String knownPluginPath = "";
897     Iterator pluginIter = getKnownPlugins().iterator();
898     while(pluginIter.hasNext()){
899       URL aPluginURL = (URL)pluginIter.next();
900       //do not save installed plug-ins - they get loaded automatically
901       if(aPluginURL.getProtocol().equals("file")){
902         File pluginDirectory = new File(aPluginURL.getFile());
903         try{
904           if(pluginDirectory.getCanonicalPath().startsWith(pluginsHomeStr)) continue;
905         }catch(IOException ioe){
906           throw new GateRuntimeException("Problem while locating the plug-in" + 
907                   aPluginURL.toString(),
908                   ioe);
909         }
910       }
911       if(knownPluginPath.length() > 0) knownPluginPath += ";";
912       knownPluginPath += aPluginURL.toExternalForm();
913     }
914     getUserConfig().put(KNOWN_PLUGIN_PATH_KEY, knownPluginPath);
915     
916     //update the autoload plugin list
917     String loadPluginPath = "";
918     pluginIter = getAutoloadPlugins().iterator();
919     while(pluginIter.hasNext()){
920       URL aPluginURL = (URL)pluginIter.next();
921       if(loadPluginPath.length() > 0) loadPluginPath += ";";
922       loadPluginPath += aPluginURL.toExternalForm();
923     }
924     getUserConfig().put(AUTOLOAD_PLUGIN_PATH_KEY, loadPluginPath);
925     
926     // the user's config file
927     String configFileName = getUserConfigFileName();
928     File configFile = new File(configFileName);
929 
930     // create if not there, then update
931     try {
932       // if the file doesn't exist, create one with an empty GATECONFIG
933       if(! configFile.exists()) {
934         FileWriter writer = new FileWriter(configFile);
935         writer.write(emptyConfigFile);
936         writer.close();
937       }
938 
939       // update the config element of the file
940       Files.updateXmlElement(
941         new File(configFileName), userConfigElement, userConfig
942       );
943 
944     } catch(IOException e) {
945       throw new GateException(
946         "problem writing user " + GATE_DOT_XML + ": " + nl + e.toString()
947       );
948     }
949   } // writeUserConfig
950 
951   /**
952    * Get the name of the user's <TT>gate.xml</TT> config file (this
953    * doesn't guarantee that file exists!).
954    */
955   public static String getUserConfigFileName() {
956     String filePrefix = "";
957     if(runningOnUnix()) filePrefix = ".";
958 
959     String userConfigName =
960       System.getProperty("user.home") + Strings.getFileSep() +
961       filePrefix + GATE_DOT_XML;
962     return userConfigName;
963   } // getUserConfigFileName
964 
965   /**
966    * Get the name of the user's <TT>gate.ser</TT> session state file (this
967    * doesn't guarantee that file exists!).
968    */
969   public static String getUserSessionFileName() {
970     String filePrefix = "";
971     if(runningOnUnix()) filePrefix = ".";
972 
973     String userSessionName =
974       System.getProperty("user.home") + Strings.getFileSep() +
975       filePrefix + GATE_DOT_SER;
976     return userSessionName;
977   } // getUserSessionFileName
978 
979   /**
980    * This method tries to guess if we are on a UNIX system. It does this
981    * by checking the value of <TT>System.getProperty("file.separator")</TT>;
982    * if this is "/" it concludes we are on UNIX. <B>This is obviously not
983    * a very good idea in the general case, so nothing much should be made
984    * to depend on this method (e.g. just naming of config file
985    * <TT>.gate.xml</TT> as opposed to <TT>gate.xml</TT>)</B>.
986    */
987   public static boolean runningOnUnix() {
988     return Strings.getFileSep().equals("/");
989   } // runningOnUnix
990 
991   /**
992    * Returns the list of CREOLE directories the system knows about (either 
993    * pre-installed plugins in the plugins directory or CREOLE directories that
994    * have previously been loaded manually).
995    * @return a {@link List} of {@link URL}s.
996    */
997   public static List getKnownPlugins(){
998     return knownPlugins;
999   }
1000  
1001  /**
1002   * Adds the plugin to the list of known plugins.
1003   * @param pluginURL the URL for the new plugin.
1004   */
1005  public static void addKnownPlugin(URL pluginURL){
1006    pluginURL = normaliseCreoleUrl(pluginURL);
1007    if(knownPlugins.contains(pluginURL)) return;
1008    knownPlugins.add(pluginURL);
1009  }
1010
1011  /**
1012   * Makes sure the provided URL ends with "/" (CREOLE URLs always point to 
1013   * directories so thry should always end with a slash.
1014   * @param url the URL to be normalised
1015   * @return the (maybe) corrected URL.
1016   */
1017  private static URL normaliseCreoleUrl(URL url){
1018    //CREOLE URLs are directory URLs so they should end with "/"
1019    String urlName = url.toExternalForm();
1020    String separator = "/";
1021    if(urlName.endsWith(separator)){
1022      return url;
1023    }else{
1024      urlName += separator;
1025      try{
1026        return new URL(urlName);
1027      }catch(MalformedURLException mue){
1028        throw new GateRuntimeException(mue);
1029      }
1030    }
1031  }
1032  
1033  /**
1034   * Returns the list of CREOLE directories the system loads automatically at
1035   * start-up.
1036   * @return a {@link List} of {@link URL}s.
1037   */
1038  public static List getAutoloadPlugins(){
1039    return autoloadPlugins;
1040  }
1041  
1042  /**
1043   * Adds a new directory to the list of plugins that are loaded automatically
1044   * at start-up.
1045   * @param pluginUrl the URL for the new plugin.
1046   */
1047  public static void addAutoloadPlugin(URL pluginUrl){
1048    pluginUrl = normaliseCreoleUrl(pluginUrl);
1049    if(autoloadPlugins.contains(pluginUrl))return;
1050    //make sure it's known
1051    addKnownPlugin(pluginUrl);
1052    //add it to autoload list
1053    autoloadPlugins.add(pluginUrl);
1054  }
1055  
1056  /**
1057   * Gets the information about a known directory.
1058   * @param directory the URL for the directory in question.
1059   * @return a {@link DirectoryInfo} value.
1060   */
1061  public static DirectoryInfo getDirectoryInfo(URL directory){
1062    if(!knownPlugins.contains(directory)) return null;
1063    DirectoryInfo dInfo = (DirectoryInfo)pluginData.get(directory);
1064    if(dInfo == null){
1065      dInfo = new DirectoryInfo(directory);
1066      pluginData.put(directory, dInfo);
1067    }    
1068    return dInfo;
1069  }
1070  
1071  /**
1072   * Tells the system to &quot;forget&quot; about one previously known 
1073   * directory. If the specified directory was loaded, it will be unloaded as 
1074   * well - i.e. all the metadata relating to resources defined by this 
1075   * directory will be removed from memory.
1076   * @param pluginURL
1077   */
1078  public static void removeKnownPlugin(URL pluginURL){
1079    pluginURL = normaliseCreoleUrl(pluginURL);
1080    knownPlugins.remove(pluginURL);
1081    autoloadPlugins.remove(pluginURL);
1082    creoleRegister.removeDirectory(pluginURL);
1083    pluginData.remove(pluginURL);
1084  }  
1085  
1086  /**
1087   * Tells the system to remove a plugin URL from the list of plugins that are
1088   * loaded automatically at system start-up. This will be reflected in the 
1089   * user's configuration data file.
1090   * @param pluginURL the URL to be removed.
1091   */
1092  public static void removeAutoloadPlugin(URL pluginURL){
1093    pluginURL = normaliseCreoleUrl(pluginURL);
1094    autoloadPlugins.remove(pluginURL);
1095  }  
1096  
1097  /**
1098   * Stores information about the contents of a CREOLE directory.
1099   */
1100  public static class DirectoryInfo{
1101    public DirectoryInfo(URL url){
1102      this.url = url;
1103      valid = true;
1104      resourceInfoList = new ArrayList();
1105      //this may invalidate it if something goes wrong
1106      parseCreole();
1107    }
1108    
1109    /**
1110     * Performs a shallow parse of the creole.xml file to get the information 
1111     * about the resources contained.
1112     */
1113    protected void parseCreole(){
1114      SAXBuilder builder = new SAXBuilder(false);
1115      try{
1116        if(!url.getPath().endsWith("/")) 
1117          url = new URL(url.getProtocol(), url.getHost(),
1118                  url.getPort(), url.getPath() + "/");
1119        URL creoleFileURL = new URL(url, "creole.xml");
1120        org.jdom.Document creoleDoc = builder.build(creoleFileURL);
1121        List jobsList = new ArrayList();
1122        jobsList.add(creoleDoc.getRootElement());
1123        while(!jobsList.isEmpty()){
1124          Element currentElem = (Element)jobsList.remove(0);
1125          if(currentElem.getName().equalsIgnoreCase("RESOURCE")){
1126            //we don't go deeper than resources so no recursion here
1127            String resName = currentElem.getChildTextTrim("NAME");
1128            String resClass = currentElem.getChildTextTrim("CLASS");
1129            String resComment = currentElem.getChildTextTrim("COMMENT");
1130            //create the handler
1131            ResourceInfo rHandler = new ResourceInfo(resName, resClass, 
1132                    resComment);
1133            resourceInfoList.add(rHandler);
1134          }else{
1135            //this is some higher level element -> simulate recursion
1136            //we want Depth-first-search so we need to add at the beginning
1137            List newJobsList = new ArrayList(currentElem.getChildren());
1138            newJobsList.addAll(jobsList);
1139            jobsList = newJobsList;
1140          }
1141        }
1142      }catch(IOException ioe){
1143        valid = false;
1144        ioe.printStackTrace();
1145      }catch(JDOMException jde){
1146        valid = false;
1147        jde.printStackTrace();
1148      }
1149    }
1150    
1151    /**
1152     * @return Returns the resourceInfoList.
1153     */
1154    public List getResourceInfoList(){
1155      return resourceInfoList;
1156    }
1157    /**
1158     * @return Returns the url.
1159     */
1160    public URL getUrl(){
1161      return url;
1162    }
1163    /**
1164     * @return Returns the valid.
1165     */
1166    public boolean isValid(){
1167      return valid;
1168    }
1169    /**
1170     * The URL for the CREOLE directory. 
1171     */
1172    protected URL url;
1173    
1174    /**
1175     * Is the directory valid (i.e. is the location reachable and the 
1176     * creole.xml file parsable).
1177     */
1178    protected boolean valid;
1179    
1180    /**
1181     * The list of {@link Gate.ResourceInfo} objects.
1182     */
1183    protected List resourceInfoList;
1184  }
1185  
1186  /**
1187   * Stores information about a resource defined by a CREOLE directory. The 
1188   * resource might not have been loaded in the system so not all information
1189   * normally provided by the {@link ResourceData} class is available. This is 
1190   * what makes this class different from {@link ResourceData}. 
1191   */
1192  public static class ResourceInfo{
1193    public ResourceInfo(String name, String className, String comment){
1194      this.resourceClassName = className;
1195      this.resourceName = name;
1196      this.resourceComment = comment;
1197    }
1198    
1199    /**
1200     * @return Returns the resourceClassName.
1201     */
1202    public String getResourceClassName(){
1203      return resourceClassName;
1204    }
1205    /**
1206     * @return Returns the resourceComment.
1207     */
1208    public String getResourceComment(){
1209      return resourceComment;
1210    }
1211    /**
1212     * @return Returns the resourceName.
1213     */
1214    public String getResourceName(){
1215      return resourceName;
1216    }
1217    /**
1218     * The class for the resource.
1219     */
1220    protected String resourceClassName;
1221    
1222    /**
1223     * The resource name.
1224     */
1225    protected String resourceName;
1226    
1227    /**
1228     * The comment for the resource.
1229     */
1230    protected String resourceComment;
1231  }
1232  
1233  /**
1234   * The top level directory of the GATE installation.
1235   */
1236  protected static File gateHome;
1237
1238  
1239  /** Site config file */
1240  private static File siteConfigFile;
1241
1242  /** User config file */
1243  private static File userConfigFile;
1244
1245
1246  /**
1247   * The top level directory for GATE installed plugins.
1248   */
1249  protected static File pluginsHome;
1250  
1251  /**
1252   * The list of plugins (aka CREOLE directories) the system knows about.
1253   * This list contains URL objects.
1254   */
1255  protected static List knownPlugins;
1256  
1257  /**
1258   * The list of plugins (aka CREOLE directories) the system loads automatically
1259   * at start-up.
1260   * This list contains URL objects.
1261   */
1262  protected static List autoloadPlugins;
1263  
1264  
1265  /**
1266   * Map from URL of directory to {@link DirectoryInfo}.
1267   */
1268  protected static Map pluginData;
1269  
1270  
1271  
1272  /** Flag for SLUG GUI start instead of standart GATE GUI. */
1273  private static boolean slugGui = false;
1274
1275  /** Should we start SLUG GUI. */
1276  public static boolean isSlugGui() { return slugGui; }
1277
1278  /** Tell GATE whether to start SLUG GUI. */
1279  public static void setSlugGui(boolean b) { slugGui = b; }
1280
1281  /** Flag for Jape Debugger integration. */
1282  private static boolean enableJapeDebug = true;
1283
1284  /** Should we enable Jape Debugger. */
1285  public static boolean isEnableJapeDebug() { return enableJapeDebug; }
1286
1287  /** Tell GATE whether to enable Jape Debugger. */
1288  public static void setEnableJapeDebug(boolean b) { enableJapeDebug = b; }
1289  
1290  /**
1291   * Flag for whether to use native serialization or xml serialization when
1292   * saving applications.
1293   */
1294  private static boolean useXMLSerialization = true;
1295  
1296  /**
1297   * Tell GATE whether to use XML serialization for applications.
1298   */
1299  public static void setUseXMLSerialization(boolean useXMLSerialization) {
1300    Gate.useXMLSerialization = useXMLSerialization;
1301  }
1302  
1303  /**
1304   * Should we use XML serialization for applications.
1305   */
1306  public static boolean getUseXMLSerialization() {
1307    return useXMLSerialization;
1308  }
1309  
1310} // class Gate
1311