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