1   /*
2    *  Main.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, 1/Nov/00
12   *
13   *  $Id: Main.java,v 1.55 2006/04/07 13:09:52 valyt Exp $
14   */
15  
16  package gate;
17  
18  import java.awt.*;
19  import java.io.*;
20  import java.net.MalformedURLException;
21  import java.net.URL;
22  import java.util.*;
23  import java.util.List;
24  
25  import javax.swing.*;
26  
27  import gate.gui.*;
28  import gate.util.*;
29  
30  import gnu.getopt.Getopt;
31  
32  
33  /** Top-level entry point for the GATE command-line and GUI interfaces.
34    * <P>
35    */
36  public class Main {
37  
38    /** Debug flag */
39    private static final boolean DEBUG = false;
40  
41    /** Status flag for normal exit. */
42    private static final int STATUS_NORMAL = 0;
43  
44    /** Status flag for error exit. */
45    private static final int STATUS_ERROR = 1;
46  
47    /** Main routine for GATE.
48      * Command-line arguments:
49      * <UL>
50      * <LI>
51      * <B>-h</B> display a short help message
52      * <LI>
53      * <B>-d URL</B> define URL to be a location for CREOLE resoures
54      * <LI>
55      * <B>-i file</B> additional initialisation file (probably called
56      *   <TT>gate.xml</TT>). Used for site-wide initialisation by the
57      *   start-up scripts
58      * <LI>
59      * <B>-a</B> run the DB administration tool
60      * </UL>
61      */
62    public static void main(String[] args) throws GateException {
63      Main.annotatorArgsMap = null;
64      // check we have a useable JDK
65      if(
66        System.getProperty("java.version").compareTo(Gate.getMinJdkVersion())
67        < 0
68      ) {
69        throw new GateException(
70          "GATE requires JDK " + Gate.getMinJdkVersion() + " or newer"
71        );
72      }
73  
74      // process command-line options
75      processArgs(args);
76  
77      // GATE builtins should be loaded from the jar (or classes dir), not
78      // from a web server (we load them over the web during testing to
79      // make sure that users can load their own that way)
80      Gate.setNetConnected(false);
81      Gate.setLocalWebServer(false);
82  
83  
84      // run the interface or do batch processing
85      if(batchMode) {
86        if(DEBUG) Out.prln("running batch process");
87        batchProcess();
88      } else if(dbAdminMode) {
89        if(DEBUG) Out.prln("running dbAdmin");
90        dbAdmin();
91      } else {
92        runGui();
93      }
94    } // main
95  
96    /** Register any CREOLE URLs that we got on the command line */
97    private static void registerCreoleUrls() {
98      CreoleRegister reg = Gate.getCreoleRegister();
99      Iterator iter = pendingCreoleUrls.iterator();
100     while(iter.hasNext()) {
101       URL u = (URL) iter.next();
102       try {
103         reg.registerDirectories(u);
104       } catch(GateException e) {
105         Err.prln("Couldn't register CREOLE directory: " + u);
106         Err.prln(e);
107         System.exit(STATUS_ERROR);
108       }
109     }
110   } // registerCreoleUrls()
111 
112   /** Main Frame of the GUI; null when no GUI running */
113   private static MainFrame frame;
114 
115   /** The splash shown when Gate starts*/
116   private static Splash splash;
117 
118   /**
119    * Get the main frame of the GUI. If the GUI isn't running, it
120    * is started.
121    */
122   public static MainFrame getMainFrame() throws GateException {
123     if(frame == null)
124       runGui();
125     return frame;
126   } // getMainFrame()
127 
128   /** Run the user interface. */
129   private static void runGui() throws GateException {
130 
131     Thread.currentThread().setPriority(Thread.MIN_PRIORITY);
132     //show the splash
133     SwingUtilities.invokeLater(new Runnable(){
134       public void run(){
135         //build the Splash
136         JPanel splashBox = new JPanel();
137         splashBox.setLayout(new GridBagLayout());
138         splashBox.setBackground(Color.white);
139         
140         GridBagConstraints constraints = new GridBagConstraints();
141         constraints.insets = new Insets(2, 2, 2, 2);
142         
143         String splashName =
144           System.getProperty(GateConstants.APP_SPLASH_JAVA_PROPERTY_NAME);
145         if(splashName == null) {
146           splashName = "gateSplash.gif";
147         } // if
148 
149         constraints.gridy = 0;
150         constraints.gridwidth = 2;
151         constraints.fill = GridBagConstraints.NONE;
152         constraints.anchor = GridBagConstraints.CENTER;
153         JLabel gifLbl = new JLabel(MainFrame.getIcon("gateHeader.gif"));
154         splashBox.add(gifLbl, constraints);
155         
156         constraints.gridy = 1;
157         constraints.gridwidth = 1;
158         gifLbl = new JLabel(MainFrame.getIcon(splashName));
159         splashBox.add(gifLbl, constraints);
160         gifLbl = new JLabel(MainFrame.getIcon("sponsors.gif"));
161         splashBox.add(gifLbl, constraints);
162         
163         splash = new Splash(splashBox);
164         splash.showSplash();
165       }
166     });
167 
168     // initialise the library and load user CREOLE directories
169     try{
170       Gate.init();
171     }catch(Throwable t){
172       int selection = JOptionPane.showOptionDialog(
173         null,
174         "Error during initialisation:\n" + t.toString() +
175         "\nDo you still want to start GATE?",
176         "GATE", JOptionPane.YES_NO_OPTION, JOptionPane.ERROR_MESSAGE,
177         null, new String[]{"Cancel", "Start anyway"},
178         "Cancel");
179       if(selection != 1){
180         t.printStackTrace();
181         System.exit(1);
182       }
183     }
184     
185 
186     //create the main frame, show it and hide the splash
187     SwingUtilities.invokeLater(new Runnable(){
188       public void run(){
189         //this needs to run before any GUI component is constructed.
190         //the initial gate splash is exempted from this rule.
191         applyUserPreferences();
192 
193         //all the defaults tables have been updated; build the GUI
194         if(Gate.isSlugGui()) {
195           frame = new ShellSlacFrame();
196           if(DEBUG) Out.prln("constructing SLUG GUI");
197         }
198         else {
199           frame = new MainFrame();
200           if(DEBUG) Out.prln("constructing GUI");
201         } // if - SLUG
202 
203         // run the GUI
204         frame.setTitleChangable(true);
205         if(Gate.isSlugGui()) {
206           frame.setTitle("SLUG application");
207         }
208         else {
209           frame.setTitle(name + " " + version + " build " + build);
210         } // if - SLUG
211 
212         // Set title from Java properties
213         String title =
214           System.getProperty(GateConstants.TITLE_JAVA_PROPERTY_NAME);
215         if(title != null) {
216           frame.setTitle(title);
217         } // if
218         frame.setTitleChangable(false);
219 
220         // Set icon from Java properties
221         // iconName could be absolute or "gate:/img/....gif"
222         String iconName =
223           System.getProperty(GateConstants.APP_ICON_JAVA_PROPERTY_NAME);
224         if(iconName != null) {
225           try {
226             frame.setIconImage(Toolkit.getDefaultToolkit().getImage(
227                   new URL(iconName)));
228           } catch(MalformedURLException mue){
229             mue.printStackTrace(Err.getPrintWriter());
230           }
231         } // if
232 
233         // Validate frames that have preset sizes
234         frame.validate();
235 
236         // Center the window
237         Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
238         Dimension frameSize = frame.getSize();
239         if (frameSize.height > screenSize.height) {
240           frameSize.height = screenSize.height;
241         }
242         if (frameSize.width > screenSize.width) {
243           frameSize.width = screenSize.width;
244         }
245         frame.setLocation((screenSize.width - frameSize.width) / 2,
246                           (screenSize.height - frameSize.height) / 2);
247 
248         frame.setVisible(true);
249         if(splash != null) splash.setVisible(false);
250 
251         if(!Gate.isSlugGui()) {
252           //load session if required and available;
253           //do everything from a new thread.
254           Runnable runnable = new Runnable(){
255             public void run(){
256               try{
257                 File sessionFile = new File(Gate.getUserSessionFileName());
258                 if(sessionFile.exists()){
259                   MainFrame.lockGUI("Loading saved session...");
260                   gate.util.persistence.PersistenceManager.loadObjectFromFile(sessionFile);
261                 }
262               }catch(Exception e){
263                 Err.prln("Failed to load session data:");
264                 e.printStackTrace(Err.getPrintWriter());
265               }finally{
266                 MainFrame.unlockGUI();
267               }
268             }
269           };
270           Thread thread = new Thread(Thread.currentThread().getThreadGroup(),
271                                      runnable, "Session loader");
272           thread.setPriority(Thread.MIN_PRIORITY);
273           thread.start();
274         } // if - when no SLUG GUI load session
275       }
276     });
277     registerCreoleUrls();
278   } // runGui()
279 
280   /** Run the db admin interface. */
281   private static void dbAdmin() throws GateException {
282     try { UserGroupEditor.main(null); } catch(Exception e) {
283       throw new GateException(e);
284     }
285   } // dbAdmin()
286 
287   /**
288    * Reads the user config data and applies the required settings.
289    */
290   protected static void applyUserPreferences(){
291     //look and feel
292     String lnfClassName = Gate.getUserConfig().
293                           getString(GateConstants.LOOK_AND_FEEL);
294     if(lnfClassName == null){
295       //if running on Linux, default to Metal rather than GTK because GTK LnF
296       //doesn't play nicely with most Gnome themes
297       if(System.getProperty("os.name").toLowerCase().indexOf("linux") != -1){
298         //running on Linux
299         lnfClassName = UIManager.getCrossPlatformLookAndFeelClassName();
300       }else{
301         lnfClassName = UIManager.getSystemLookAndFeelClassName();
302       }
303       Gate.getUserConfig().put(GateConstants.LOOK_AND_FEEL, lnfClassName);
304     }
305     try {
306       UIManager.setLookAndFeel(lnfClassName);
307     } catch(Exception e) {
308       throw new gate.util.GateRuntimeException(e.toString());
309     }
310 
311     //read the user config data
312     OptionsMap userConfig = Gate.getUserConfig();
313 
314     //text font
315     Font font = userConfig.getFont(GateConstants.TEXT_COMPONENTS_FONT);
316     if(font == null){
317       String fontName = Gate.guessUnicodeFont();
318       if(fontName != null){
319         font = new Font(fontName, Font.PLAIN, 12);
320       }else{
321         font = UIManager.getFont("TextPane.font");
322       }
323     }
324 
325     if(font != null){
326       OptionsDialog.setTextComponentsFont(font);
327     }
328 
329     //menus font
330     font = userConfig.getFont(GateConstants.MENUS_FONT);
331     if(font == null){
332       String fontName = Gate.guessUnicodeFont();
333       if(fontName != null){
334         font = new Font(fontName, Font.PLAIN, 12);
335       }else{
336         font = UIManager.getFont("Menu.font");
337       }
338     }
339 
340     if(font != null){
341       OptionsDialog.setMenuComponentsFont(font);
342     }
343 
344     //other gui font
345     font = userConfig.getFont(GateConstants.OTHER_COMPONENTS_FONT);
346     if(font == null){
347       String fontName = Gate.guessUnicodeFont();
348       if(fontName != null){
349         font = new Font(fontName, Font.PLAIN, 12);
350       }else{
351         font = UIManager.getFont("Button.font");
352       }
353     }
354 
355     if(font != null){
356       OptionsDialog.setComponentsFont(font);
357     }
358 
359 
360   }
361 
362 
363 
364   // find out the version and build numbers
365   static {
366     // find out the version number
367     try {
368       InputStream ver = Files.getGateResourceAsStream("version.txt");
369       if (ver==null) {
370         throw new IOException();
371       }
372       BufferedReader reader = new BufferedReader(new InputStreamReader(ver,
373           "UTF-8"));
374       Main.version = reader.readLine();
375     } catch(IOException ioe) {
376       Main.version = "3.0";
377     }
378 
379     // find out the build number
380     try{
381       InputStream build = Files.getGateResourceAsStream("build.txt");
382       if (build==null) {
383         throw new IOException();
384       }
385       BufferedReader reader = new BufferedReader(new InputStreamReader(build,
386           "UTF-8"));
387       Main.build = reader.readLine();
388     } catch(IOException ioe) {
389       Main.build = "0000";
390     }
391   } // static initialiser finding build and version
392 
393 
394 /**
395 
396 <BR>
397 <B>Options processing: </B>
398 
399 <BR>
400 <TABLE>
401   <TR>
402     <TH ALIGN=left COLSPAN=15>
403     -a annotator arg(s)
404     </TH>
405     <TH ALIGN=left>
406     A CREOLE annotator to run on the collection, with zero or more
407     arguments. The set of such annotators will be run in the sequence
408     they appear in the arguments list. The arguments list must end with the
409     start of another option; otherwise add a "-" after the arguments to
410     terminate the list.
411     </TH>
412   </TR>
413   <TR>
414     <TH ALIGN=left COLSPAN=15>
415     -b
416     </TH>
417     <TH ALIGN=left>
418     Batch mode. Don't start the GUI, just process options and exit after
419     any actions (e.g. running annotators).
420     </TH>
421   </TR>
422   <TR>
423     <TH ALIGN=left COLSPAN=15>
424     -c collname
425     </TH>
426     <TH ALIGN=left>
427     Name of the collection to use. If the collection already exists then
428     it will be used as it stands, otherwise it will be created. See also
429     -f.
430     </TH>
431   </TR>
432   <TR>
433     <TH ALIGN=left COLSPAN=15>
434     -d
435     </TH>
436     <TH ALIGN=left>
437     Destroy the collection after use. (The default is to save it to
438     disk.)
439     </TH>
440   </TR>
441   <TR>
442     <TH ALIGN=left COLSPAN=15>
443     -f file(s)
444     </TH>
445     <TH ALIGN=left>
446     One or more files to create a collection with. If the collection
447     being used (see -c) already exists, these files are ignored.
448     Otherwise they are used to create the collection.
449     </TH>
450   </TR>
451   <TR>
452     <TH ALIGN=left COLSPAN=15>
453     -h
454     </TH>
455     <TH ALIGN=left>
456     Print a usage message and exit.
457     </TH>
458   </TR>
459   <TR>
460     <TH ALIGN=left COLSPAN=15>
461     -p creolepath
462     </TH>
463     <TH ALIGN=left>
464     Sets the search path for CREOLE modules.
465     </TH>
466   </TR>
467   <TR>
468     <TH ALIGN=left COLSPAN=15>
469     -v classname(s)
470     </TH>
471     <TH ALIGN=left>
472     Verbose: turns on debugging output. Takes zero or more class names
473     to debug.
474     </TH>
475   </TR>
476 </TABLE>
477 
478 */
479   /** Name of the collection we were asked to process. */
480   private static String collName;
481 
482   /** Search path for CREOLE modules. */
483   private static String creolePath;
484 
485   /** List of files we were asked to build a collection from. */
486   private static List fileNames = new ArrayList();
487 
488   /** List of annotators we were asked to run on the collection. */
489   private static List annotatorNames = new ArrayList();
490 
491   /** Map of annotator arguments. */
492   private static Map annotatorArgsMap = new HashMap();
493 
494   /** List of classes we were asked to debug. */
495   private static List debugNames = new ArrayList();
496 
497   /** Are we in batch mode? */
498   public static boolean batchMode = false;
499 
500   /** Are we in db admin mode? */
501   public static boolean dbAdminMode = false;
502 
503   /** Don't save collection after batch? */
504   private static boolean destroyColl = false;
505 
506   /** Verbose? */
507   private static boolean verbose = false;
508 
509   private static boolean runCorpusBenchmarkTool = false;
510 
511   public static String name = "GATE";
512   public static String version;
513   public static String build;
514 
515   /** Process arguments and set up member fields appropriately.
516     * Will shut down the process (via System.exit) if there are
517     * incorrect arguments, or if the arguments ask for something
518     * simple like printing the help message.
519     */
520   public static void processArgs(String[] args) {
521 
522     Getopt g = new Getopt("GATE main", args, "hd:ei:asj");
523     int c;
524     while( (c = g.getopt()) != -1 )
525       switch(c) {
526         // -a
527         case 'a':
528           dbAdminMode = true;
529           break;
530         // -h
531         case 'h':
532           help();
533           usage();
534           System.exit(STATUS_NORMAL);
535           break;
536         // -d creole-dir
537         case 'd':
538           String urlString = g.getOptarg();
539           URL u = null;
540           try {
541             u = new URL(urlString);
542           } catch(MalformedURLException e) {
543             Err.prln("Bad URL: " + urlString);
544             Err.prln(e);
545             System.exit(STATUS_ERROR);
546           }
547           pendingCreoleUrls.add(u);
548           Out.prln(
549             "CREOLE Directory " + urlString + " queued for registration"
550           );
551           break;
552         // -i gate.xml site-wide init file
553         case 'i':
554           String optionString = g.getOptarg();
555           URL u2 = null;
556           File f = new File(optionString);
557           try {
558             u2 = f.toURL();
559           } catch(MalformedURLException e) {
560             Err.prln("Bad initialisation file: " + optionString);
561             Err.prln(e);
562             System.exit(STATUS_ERROR);
563           }
564           Gate.setSiteConfigFile(f);
565           if(DEBUG)
566             Out.prln(
567               "Initialisation file " + optionString +
568               " recorded for initialisation"
569             );
570           break;
571         // -e runs the CorpusBenchmarkTool (e for evaluate)
572         case 'e':
573           try {
574             CorpusBenchmarkTool.main(args);
575           } catch (GateException ex) {
576             Out.prln("Error running the evaluation tool: " + ex.getMessage());
577             System.exit(-1);
578           }
579           break;
580         // -s runs the SLUG GUI
581         case 's':
582           Gate.setSlugGui(true);
583           break;
584           // -j enable Jape Debugger
585           case 'j':
586             Gate.setEnableJapeDebug(true);
587             break;
588 
589 
590 
591 /*
592         // -c collname
593         case '-c':
594           collName = g.getOptarg();
595           break;
596 
597         // -b
598         case '-b':
599           batchMode = true;
600           break;
601 
602         // -a annotator(s)
603         case '-a':
604           if(++i == args.length) { usage(); return; }
605           String annotatorName = g.getOptarg();
606           annotatorNames.add(annotatorName);
607 // collect any args for the annotator
608           break;
609 
610         // -d
611         case '-d':
612           destroyColl = true;
613           break;
614 
615         // -f file(s)
616         case '-f':
617           while(++i < args.length)
618             if(args[i].toCharArray()[0] == '-') { // start of another option
619               i--;
620               break;
621             }
622             else
623               fileNames.add(args[i]);
624           break;
625 
626         // -p creolepath
627         case '-p':
628           if(++i < args.length)
629             creolePath = args[i];
630           else
631             { usage(); return; }
632           break;
633 
634         // -v classname(s)
635         case '-v':
636           verbose = true;
637           Debug.setDebug(true);
638           while(++i < args.length) {
639             if(args[i].toCharArray()[0] == '-') { // start of another option
640               i--;
641               break;
642             }
643             else
644               debugNames.add(args[i]);
645           } // while
646           break;
647 */
648 
649         case '?':
650           // leave the warning to getopt
651           System.exit(STATUS_ERROR);
652           break;
653 
654         default:
655           // shouldn't happen!
656           Err.prln("getopt() returned " + c + "\n");
657           System.exit(STATUS_ERROR);
658           break;
659       } // getopt switch
660 
661   } // processArgs()
662 
663   /** Run commands as a batch process. */
664   private static void batchProcess() throws GateException{
665     // initialise the library and load user CREOLE directories
666     Gate.init();
667     registerCreoleUrls();
668 
669 /*
670     // turn debugging on where requested
671     if(verbose) {
672       for(ArrayIterator i = debugNames.begin(); ! i.atEnd(); i.advance()) {
673         try { Debug.setDebug(Class.forName(((String) i.get())), true); }
674         catch(ClassNotFoundException e) {
675           System.err.println(
676             "can't debug class " + (String) i.get() + ": " + e.toString()
677           );
678         }
679       } // for
680     } // debugging on
681 
682     // collection: does it exist and can we open it?
683     if(collName == null) {
684       System.err.println("no collection name given");
685       usage();
686       return;
687     }
688     File collDir = new File(collName);
689     JdmCollection coll = null;
690     if(collDir.exists()) { // open collection
691       Debug.prnl("opening collection " + collName);
692       try {
693         coll = new JdmCollection(collName);
694       } catch (JdmException e) {
695         System.err.println(
696           "Couldn't open collection " + collName + " " + e.toString()
697         );
698         return;
699       }
700     } else { // create collection and add documents
701       Debug.prnl("creating collection " + collName);
702       JdmAttributeSequence attrs = new JdmAttributeSequence();
703       try {
704         coll = new JdmCollection(collName, attrs);
705       } catch (JdmException e) {
706         System.err.println(
707           "Couldn't create collection " + collName + " " + e.toString()
708         );
709         return;
710       }
711 
712       // add the documents to the collection
713       for(ArrayIterator i = fileNames.begin(); ! i.atEnd(); i.advance()) {
714         Debug.prnl("adding document " + (String) i.get());
715         try {
716           JdmDocument doc = coll.createDocument(
717             (String) i.get(),
718             null,
719             new JdmAnnotationSet(),
720             new JdmAttributeSequence()
721           );
722         } catch (JdmException e) {
723           System.err.println(
724              "Can't add document " + (String) i.get() + ": " + e.toString()
725           );
726         } // catch
727       } // for each filename
728     } // collection create
729 
730     // run the annotators on each document in the collection
731     // for each document
732     JdmDocument doc = null;
733     if(coll.length() > 0)
734       try{ doc = coll.firstDocument(); } catch(JdmException e) { }
735     for(int i = 0; i<coll.length(); i++) {
736       if(doc == null) continue; // first and next doc shouldn't throw excptns!
737 
738       // for each annotator
739       for(ArrayIterator j = annotatorNames.begin(); !j.atEnd(); j.advance()) {
740         String annotatorName = (String) j.get();
741         Debug.prnl(
742           "calling annotator " + annotatorName + " on doc " + doc.getId()
743         );
744 
745         // load the annotator class
746         Annotator annotator = null;
747         Class annotatorClass = null;
748         try {
749           // cheat and assume that all annotators are on CLASSPATH
750           annotatorClass = Class.forName(annotatorName);
751         } catch (Exception ex) {
752           System.err.println(
753             "Could load class for CREOLE object " + annotatorName + ": " +
754             ex.toString()
755           );
756           continue;
757         }
758 
759         // construct the annotator
760         try {
761           annotator = (Annotator) annotatorClass.newInstance();
762         } catch (Throwable ex) { // naughty chap
763           System.err.println(
764             "Could create instance of CREOLE object " + annotatorName + ": " +
765             ex.toString()
766           );
767           continue;
768         }
769 
770         // annotate this document
771         String[] args = (String[]) annotatorArgsMap.get(annotatorName);
772         if(args == null) args = new String[0];
773         annotator.annotate(doc, args);
774       } // for each annotator
775 
776       doc = null;
777       try { doc = coll.nextDocument(); } catch(JdmException e) { }
778     } // for each doc, annotate
779 
780     // save collection?
781     if(! destroyColl) {
782       Debug.prnl("saving the collection");
783       try {
784         coll.sync();
785       } catch (JdmException e) {
786         System.err.println(
787           "Can't save collection " + collName + ": " + e.toString()
788         );
789       }
790     } else {
791       Debug.prnl("destroying collection");
792       try { coll.destroy(); } catch(JdmException e) {
793         // if we didn't sync we can't destroy, but that's not an error
794       }
795     }
796 
797     Debug.prnl("done batch process");
798 */
799   } // batchProcess()
800 
801   /** Display a usage message */
802   public static void usage() {
803     Out.prln(
804       "Usage: java gate.Main " +
805       "[ -h [-d CREOLE-URL]" +
806       ""
807     );
808   } // usage()
809 
810   /** Display a help message */
811   public static void help() {
812     String nl = Strings.getNl();
813     Out.prln(
814       "For help on command-line options and other information " + nl +
815       "see the user manual in your GATE distribution or at " + nl +
816       "http://gate.ac.uk/sale/tao/"
817     );
818   } // help()
819 
820   /** The list of pending URLs to add to the CREOLE register */
821   private static List pendingCreoleUrls = new ArrayList();
822 
823 } // class Main
824