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.42 2003/02/20 18:40:12 valyt 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      new Main().annotatorArgsMap = null;
66      // check we have a useable JDK
67      if(
68        System.getProperty("java.version").compareTo(Gate.getMinJdkVersion())
69        < 0
70      ) {
71        throw new GateException(
72          "GATE requires JDK " + Gate.getMinJdkVersion() + " or newer"
73        );
74      }
75  
76      // process command-line options
77      processArgs(args);
78  
79      // GATE builtins should be loaded from the jar (or classes dir), not
80      // from a web server (we load them over the web during testing to
81      // make sure that users can load their own that way)
82      Gate.setNetConnected(false);
83      Gate.setLocalWebServer(false);
84  
85  
86      // run the interface or do batch processing
87      if(batchMode) {
88        if(DEBUG) Out.prln("running batch process");
89        batchProcess();
90      } else if(dbAdminMode) {
91        if(DEBUG) Out.prln("running dbAdmin");
92        dbAdmin();
93      } else {
94        runGui();
95      }
96    } // main
97  
98    /** Register any CREOLE URLs that we got on the command line */
99    private static void registerCreoleUrls() {
100     CreoleRegister reg = Gate.getCreoleRegister();
101     Iterator iter = pendingCreoleUrls.iterator();
102     while(iter.hasNext()) {
103       URL u = (URL) iter.next();
104       try {
105         reg.registerDirectories(u);
106       } catch(GateException e) {
107         Err.prln("Couldn't register CREOLE directory: " + u);
108         Err.prln(e);
109         System.exit(STATUS_ERROR);
110       }
111     }
112   } // registerCreoleUrls()
113 
114   /** Main Frame of the GUI; null when no GUI running */
115   private static MainFrame frame;
116 
117   /** The splash shown when Gate starts*/
118   private static Splash splash;
119 
120   /**
121    * Get the main frame of the GUI. If the GUI isn't running, it
122    * is started.
123    */
124   public static MainFrame getMainFrame() throws GateException {
125     if(frame == null)
126       runGui();
127     return frame;
128   } // getMainFrame()
129 
130   /** Run the user interface. */
131   private static void runGui() throws GateException {
132 
133     Thread.currentThread().setPriority(Thread.MIN_PRIORITY);
134     //show the splash
135     SwingUtilities.invokeLater(new Runnable(){
136       public void run(){
137         //build the Spash
138         JPanel splashBox = new JPanel();
139         splashBox.setLayout(new BoxLayout(splashBox, BoxLayout.Y_AXIS));
140         splashBox.setBackground(Color.white);
141 
142         String splashName =
143           System.getProperty(GateConstants.APP_SPLASH_JAVA_PROPERTY_NAME);
144         if(splashName == null) {
145           splashName = "gateSplash.gif";
146         } // if
147 
148         JLabel gifLbl = new JLabel(new ImageIcon(Main.class.getResource(
149             "/gate/resources/img/"+splashName)));
150         Box box = new Box(BoxLayout.X_AXIS);
151         box.add(Box.createHorizontalGlue());
152         box.add(gifLbl);
153         box.add(Box.createHorizontalGlue());
154         splashBox.add(box);
155         gifLbl = new JLabel(new ImageIcon(Main.class.getResource(
156             "/gate/resources/img/gateHeader.gif")));
157         box = new Box(BoxLayout.X_AXIS);
158         box.add(Box.createHorizontalGlue());
159         box.add(gifLbl);
160         box.add(Box.createHorizontalGlue());
161         splashBox.add(box);
162         splashBox.add(Box.createVerticalStrut(15));
163         splash = new Splash(splashBox);
164         splash.show();
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     registerCreoleUrls();
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.hide();
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   } // runGui()
278 
279   /** Run the db admin interface. */
280   private static void dbAdmin() throws GateException {
281     try { UserGroupEditor.main(null); } catch(Exception e) {
282       throw new GateException(e);
283     }
284   } // dbAdmin()
285 
286   /**
287    * Reads the user config data and applies the required settings.
288    */
289   protected static void applyUserPreferences(){
290     //look and feel
291     String lnfClassName = Gate.getUserConfig().
292                           getString(GateConstants.LOOK_AND_FEEL);
293     if(lnfClassName == null){
294       lnfClassName = UIManager.getSystemLookAndFeelClassName();
295       Gate.getUserConfig().put(GateConstants.LOOK_AND_FEEL, lnfClassName);
296     }
297     try {
298       UIManager.setLookAndFeel(lnfClassName);
299     } catch(Exception e) {
300       throw new gate.util.GateRuntimeException(e.toString());
301     }
302 
303     //read the user config data
304     OptionsMap userConfig = Gate.getUserConfig();
305 
306     //text font
307     Font font = userConfig.getFont(GateConstants.TEXT_COMPONENTS_FONT);
308     if(font == null){
309       String fontName = Gate.guessUnicodeFont();
310       if(fontName != null){
311         font = new Font(fontName, Font.PLAIN, 12);
312       }else{
313         font = UIManager.getFont("TextPane.font");
314       }
315     }
316 
317     if(font != null){
318       OptionsDialog.setTextComponentsFont(font);
319     }
320 
321     //menus font
322     font = userConfig.getFont(GateConstants.MENUS_FONT);
323     if(font == null){
324       String fontName = Gate.guessUnicodeFont();
325       if(fontName != null){
326         font = new Font(fontName, Font.PLAIN, 12);
327       }else{
328         font = UIManager.getFont("Menu.font");
329       }
330     }
331 
332     if(font != null){
333       OptionsDialog.setMenuComponentsFont(font);
334     }
335 
336     //other gui font
337     font = userConfig.getFont(GateConstants.OTHER_COMPONENTS_FONT);
338     if(font == null){
339       String fontName = Gate.guessUnicodeFont();
340       if(fontName != null){
341         font = new Font(fontName, Font.PLAIN, 12);
342       }else{
343         font = UIManager.getFont("Button.font");
344       }
345     }
346 
347     if(font != null){
348       OptionsDialog.setComponentsFont(font);
349     }
350 
351 
352   }
353 
354 
355 
356   // find out the version and build numbers
357   static {
358     // find out the version number
359     try {
360       InputStream ver = Files.getGateResourceAsStream("version.txt");
361       if (ver==null) {
362         throw new IOException();
363       }
364       BufferedReader reader = new BufferedReader(new InputStreamReader(ver));
365       Main.version = reader.readLine();
366     } catch(IOException ioe) {
367       Main.version = "2.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       Main.build = reader.readLine();
378     } catch(IOException ioe) {
379       Main.build = "0000";
380     }
381   } // static initialiser finding build and version
382 
383 
384 /**
385 
386 <BR>
387 <B>Options processing: </B>
388 
389 <BR>
390 <TABLE>
391   <TR>
392     <TH ALIGN=left COLSPAN=15>
393     -a annotator arg(s)
394     </TH>
395     <TH ALIGN=left>
396     A CREOLE annotator to run on the collection, with zero or more
397     arguments. The set of such annotators will be run in the sequence
398     they appear in the arguments list. The arguments list must end with the
399     start of another option; otherwise add a "-" after the arguments to
400     terminate the list.
401     </TH>
402   </TR>
403   <TR>
404     <TH ALIGN=left COLSPAN=15>
405     -b
406     </TH>
407     <TH ALIGN=left>
408     Batch mode. Don't start the GUI, just process options and exit after
409     any actions (e.g. running annotators).
410     </TH>
411   </TR>
412   <TR>
413     <TH ALIGN=left COLSPAN=15>
414     -c collname
415     </TH>
416     <TH ALIGN=left>
417     Name of the collection to use. If the collection already exists then
418     it will be used as it stands, otherwise it will be created. See also
419     -f.
420     </TH>
421   </TR>
422   <TR>
423     <TH ALIGN=left COLSPAN=15>
424     -d
425     </TH>
426     <TH ALIGN=left>
427     Destroy the collection after use. (The default is to save it to
428     disk.)
429     </TH>
430   </TR>
431   <TR>
432     <TH ALIGN=left COLSPAN=15>
433     -f file(s)
434     </TH>
435     <TH ALIGN=left>
436     One or more files to create a collection with. If the collection
437     being used (see -c) already exists, these files are ignored.
438     Otherwise they are used to create the collection.
439     </TH>
440   </TR>
441   <TR>
442     <TH ALIGN=left COLSPAN=15>
443     -h
444     </TH>
445     <TH ALIGN=left>
446     Print a usage message and exit.
447     </TH>
448   </TR>
449   <TR>
450     <TH ALIGN=left COLSPAN=15>
451     -p creolepath
452     </TH>
453     <TH ALIGN=left>
454     Sets the search path for CREOLE modules.
455     </TH>
456   </TR>
457   <TR>
458     <TH ALIGN=left COLSPAN=15>
459     -v classname(s)
460     </TH>
461     <TH ALIGN=left>
462     Verbose: turns on debugging output. Takes zero or more class names
463     to debug.
464     </TH>
465   </TR>
466 </TABLE>
467 
468 */
469   /** Name of the collection we were asked to process. */
470   private static String collName;
471 
472   /** Search path for CREOLE modules. */
473   private static String creolePath;
474 
475   /** List of files we were asked to build a collection from. */
476   private static List fileNames = new ArrayList();
477 
478   /** List of annotators we were asked to run on the collection. */
479   private static List annotatorNames = new ArrayList();
480 
481   /** Map of annotator arguments. */
482   private static Map annotatorArgsMap = new HashMap();
483 
484   /** List of classes we were asked to debug. */
485   private static List debugNames = new ArrayList();
486 
487   /** Are we in batch mode? */
488   public static boolean batchMode = false;
489 
490   /** Are we in db admin mode? */
491   public static boolean dbAdminMode = false;
492 
493   /** Don't save collection after batch? */
494   private static boolean destroyColl = false;
495 
496   /** Verbose? */
497   private static boolean verbose = false;
498 
499   private static boolean runCorpusBenchmarkTool = false;
500 
501   public static String name = "Gate";
502   public static String version;
503   public static String build;
504 
505   /** Process arguments and set up member fields appropriately.
506     * Will shut down the process (via System.exit) if there are
507     * incorrect arguments, or if the arguments ask for something
508     * simple like printing the help message.
509     */
510   public static void processArgs(String[] args) {
511 
512     Getopt g = new Getopt("GATE main", args, "hd:ei:as");
513     int c;
514     while( (c = g.getopt()) != -1 )
515       switch(c) {
516         // -a
517         case 'a':
518           dbAdminMode = true;
519           break;
520         // -h
521         case 'h':
522           help();
523           usage();
524           System.exit(STATUS_NORMAL);
525           break;
526         // -d creole-dir
527         case 'd':
528           String urlString = g.getOptarg();
529           URL u = null;
530           try {
531             u = new URL(urlString);
532           } catch(MalformedURLException e) {
533             Err.prln("Bad URL: " + urlString);
534             Err.prln(e);
535             System.exit(STATUS_ERROR);
536           }
537           pendingCreoleUrls.add(u);
538           Out.prln(
539             "CREOLE Directory " + urlString + " queued for registration"
540           );
541           break;
542         // -i gate.xml site-wide init file
543         case 'i':
544           String optionString = g.getOptarg();
545           URL u2 = null;
546           File f = new File(optionString);
547           try {
548             u2 = f.toURL();
549           } catch(MalformedURLException e) {
550             Err.prln("Bad initialisation file: " + optionString);
551             Err.prln(e);
552             System.exit(STATUS_ERROR);
553           }
554           Gate.setSiteConfigFile(f);
555           if(DEBUG)
556             Out.prln(
557               "Initialisation file " + optionString +
558               " recorded for initialisation"
559             );
560           break;
561         // -e runs the CorpusBenchmarkTool (e for evaluate)
562         case 'e':
563           try {
564             CorpusBenchmarkTool.main(args);
565           } catch (GateException ex) {
566             Out.prln("Error running the evaluation tool: " + ex.getMessage());
567             System.exit(-1);
568           }
569           break;
570         // -s runs the SLUG GUI
571         case 's':
572           Gate.setSlugGui(true);
573           break;
574 
575 
576 
577 /*
578         // -c collname
579         case '-c':
580           collName = g.getOptarg();
581           break;
582 
583         // -b
584         case '-b':
585           batchMode = true;
586           break;
587 
588         // -a annotator(s)
589         case '-a':
590           if(++i == args.length) { usage(); return; }
591           String annotatorName = g.getOptarg();
592           annotatorNames.add(annotatorName);
593 // collect any args for the annotator
594           break;
595 
596         // -d
597         case '-d':
598           destroyColl = true;
599           break;
600 
601         // -f file(s)
602         case '-f':
603           while(++i < args.length)
604             if(args[i].toCharArray()[0] == '-') { // start of another option
605               i--;
606               break;
607             }
608             else
609               fileNames.add(args[i]);
610           break;
611 
612         // -p creolepath
613         case '-p':
614           if(++i < args.length)
615             creolePath = args[i];
616           else
617             { usage(); return; }
618           break;
619 
620         // -v classname(s)
621         case '-v':
622           verbose = true;
623           Debug.setDebug(true);
624           while(++i < args.length) {
625             if(args[i].toCharArray()[0] == '-') { // start of another option
626               i--;
627               break;
628             }
629             else
630               debugNames.add(args[i]);
631           } // while
632           break;
633 */
634 
635         case '?':
636           // leave the warning to getopt
637           System.exit(STATUS_ERROR);
638           break;
639 
640         default:
641           // shouldn't happen!
642           Err.prln("getopt() returned " + c + "\n");
643           System.exit(STATUS_ERROR);
644           break;
645       } // getopt switch
646 
647   } // processArgs()
648 
649   /** Run commands as a batch process. */
650   private static void batchProcess() throws GateException{
651     // initialise the library and load user CREOLE directories
652     Gate.init();
653     registerCreoleUrls();
654 
655 /*
656     // turn debugging on where requested
657     if(verbose) {
658       for(ArrayIterator i = debugNames.begin(); ! i.atEnd(); i.advance()) {
659         try { Debug.setDebug(Class.forName(((String) i.get())), true); }
660         catch(ClassNotFoundException e) {
661           System.err.println(
662             "can't debug class " + (String) i.get() + ": " + e.toString()
663           );
664         }
665       } // for
666     } // debugging on
667 
668     // collection: does it exist and can we open it?
669     if(collName == null) {
670       System.err.println("no collection name given");
671       usage();
672       return;
673     }
674     File collDir = new File(collName);
675     JdmCollection coll = null;
676     if(collDir.exists()) { // open collection
677       Debug.prnl("opening collection " + collName);
678       try {
679         coll = new JdmCollection(collName);
680       } catch (JdmException e) {
681         System.err.println(
682           "Couldn't open collection " + collName + " " + e.toString()
683         );
684         return;
685       }
686     } else { // create collection and add documents
687       Debug.prnl("creating collection " + collName);
688       JdmAttributeSequence attrs = new JdmAttributeSequence();
689       try {
690         coll = new JdmCollection(collName, attrs);
691       } catch (JdmException e) {
692         System.err.println(
693           "Couldn't create collection " + collName + " " + e.toString()
694         );
695         return;
696       }
697 
698       // add the documents to the collection
699       for(ArrayIterator i = fileNames.begin(); ! i.atEnd(); i.advance()) {
700         Debug.prnl("adding document " + (String) i.get());
701         try {
702           JdmDocument doc = coll.createDocument(
703             (String) i.get(),
704             null,
705             new JdmAnnotationSet(),
706             new JdmAttributeSequence()
707           );
708         } catch (JdmException e) {
709           System.err.println(
710              "Can't add document " + (String) i.get() + ": " + e.toString()
711           );
712         } // catch
713       } // for each filename
714     } // collection create
715 
716     // run the annotators on each document in the collection
717     // for each document
718     JdmDocument doc = null;
719     if(coll.length() > 0)
720       try{ doc = coll.firstDocument(); } catch(JdmException e) { }
721     for(int i = 0; i<coll.length(); i++) {
722       if(doc == null) continue; // first and next doc shouldn't throw excptns!
723 
724       // for each annotator
725       for(ArrayIterator j = annotatorNames.begin(); !j.atEnd(); j.advance()) {
726         String annotatorName = (String) j.get();
727         Debug.prnl(
728           "calling annotator " + annotatorName + " on doc " + doc.getId()
729         );
730 
731         // load the annotator class
732         Annotator annotator = null;
733         Class annotatorClass = null;
734         try {
735           // cheat and assume that all annotators are on CLASSPATH
736           annotatorClass = Class.forName(annotatorName);
737         } catch (Exception ex) {
738           System.err.println(
739             "Could load class for CREOLE object " + annotatorName + ": " +
740             ex.toString()
741           );
742           continue;
743         }
744 
745         // construct the annotator
746         try {
747           annotator = (Annotator) annotatorClass.newInstance();
748         } catch (Throwable ex) { // naughty chap
749           System.err.println(
750             "Could create instance of CREOLE object " + annotatorName + ": " +
751             ex.toString()
752           );
753           continue;
754         }
755 
756         // annotate this document
757         String[] args = (String[]) annotatorArgsMap.get(annotatorName);
758         if(args == null) args = new String[0];
759         annotator.annotate(doc, args);
760       } // for each annotator
761 
762       doc = null;
763       try { doc = coll.nextDocument(); } catch(JdmException e) { }
764     } // for each doc, annotate
765 
766     // save collection?
767     if(! destroyColl) {
768       Debug.prnl("saving the collection");
769       try {
770         coll.sync();
771       } catch (JdmException e) {
772         System.err.println(
773           "Can't save collection " + collName + ": " + e.toString()
774         );
775       }
776     } else {
777       Debug.prnl("destroying collection");
778       try { coll.destroy(); } catch(JdmException e) {
779         // if we didn't sync we can't destroy, but that's not an error
780       }
781     }
782 
783     Debug.prnl("done batch process");
784 */
785   } // batchProcess()
786 
787   /** Display a usage message */
788   public static void usage() {
789     Out.prln(
790       "Usage: java gate.Main " +
791       "[ -h [-d CREOLE-URL]" +
792       ""
793     );
794   } // usage()
795 
796   /** Display a help message */
797   public static void help() {
798     String nl = Strings.getNl();
799     Out.prln(
800       "For help on command-line options and other information " + nl +
801       "see the user manual in your GATE distribution or at " + nl +
802       "http://gate.ac.uk/gate/doc/userguide.html"
803     );
804   } // help()
805 
806   /** The list of pending URLs to add to the CREOLE register */
807   private static List pendingCreoleUrls = new ArrayList();
808 
809 } // class Main
810