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