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