source: trunk/gli/src/org/greenstone/gatherer/Gatherer.java@ 7342

Last change on this file since 7342 was 7342, checked in by kjdon, 20 years ago

was setting config.site_name to null on exit

  • Property svn:keywords set to Author Date Id Revision
File size: 47.7 KB
Line 
1/**
2 *#########################################################################
3 *
4 * A component of the Gatherer application, part of the Greenstone digital
5 * library suite from the New Zealand Digital Library Project at the
6 * University of Waikato, New Zealand.
7 *
8 * Author: John Thompson, Greenstone Digital Library, University of Waikato
9 *
10 * Copyright (C) 1999 New Zealand Digital Library Project
11 *
12 * This program is free software; you can redistribute it and/or modify
13 * it under the terms of the GNU General Public License as published by
14 * the Free Software Foundation; either version 2 of the License, or
15 * (at your option) any later version.
16 *
17 * This program is distributed in the hope that it will be useful,
18 * but WITHOUT ANY WARRANTY; without even the implied warranty of
19 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 * GNU General Public License for more details.
21 *
22 * You should have received a copy of the GNU General Public License
23 * along with this program; if not, write to the Free Software
24 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
25 *########################################################################
26 */
27package org.greenstone.gatherer;
28
29/**************************************************************************************
30 * Written: ??/??/02
31 * Revised: ??/??/02 - Commented
32 * ??/??/03 - Added support for local library server
33 * 20/07/03 - Rewrote argument parsing so that spaces no longer cause GLI to die. Also added time out when starting local library.
34 **************************************************************************************/
35
36//import com.l2fprod.gui.*;
37//import com.l2fprod.gui.plaf.skin.*;
38//import com.l2fprod.util.*;
39import java.awt.*;
40import java.awt.event.*;
41import java.io.*;
42import java.lang.*;
43import java.net.*;
44import java.util.*;
45import javax.swing.*;
46import javax.swing.plaf.*;
47import javax.swing.text.*;
48import org.greenstone.gatherer.Configuration;
49import org.greenstone.gatherer.GAuthenticator;
50import org.greenstone.gatherer.cdm.CommandTokenizer;
51import org.greenstone.gatherer.collection.CollectionManager;
52import org.greenstone.gatherer.feedback.ActionRecorderDialog;
53import org.greenstone.gatherer.file.FileManager;
54import org.greenstone.gatherer.file.FileAssociationManager;
55import org.greenstone.gatherer.gui.Coloring;
56import org.greenstone.gatherer.gui.GUIManager;
57import org.greenstone.gatherer.gui.ModalDialog;
58import org.greenstone.gatherer.gui.Splash;
59import org.greenstone.gatherer.gui.URLField;
60import org.greenstone.gatherer.gui.WarningDialog;
61import org.greenstone.gatherer.msm.MDSTest;
62import org.greenstone.gatherer.msm.MetadataSetManager;
63import org.greenstone.gatherer.util.ArrayTools;
64import org.greenstone.gatherer.util.GSDLSiteConfig;
65import org.greenstone.gatherer.util.StaticStrings;
66import org.greenstone.gatherer.util.Utility;
67import sun.misc.*;
68
69/** Containing the main() method for the Gatherer, this class is the starting point for the rest of the application. It first parses the command line arguments, preparing to update the configuration as required. Next it loads several important support classes such as the Configuration and Dictionary. Finally it creates the other important managers and sends them on their way.
70 * @author John Thompson, Greenstone Digital Library, University of Waikato
71 * @version 2.3
72 */
73
74// How to catch All Exceptions including those from the AWT Event thread.
75// Step 1: register a handler class in your main()
76//
77// System.setProperty("sun.awt.exception.handler", "YourHandler");
78//
79// Step 2: implement your handler class.
80//
81// public class YourHandler {
82// public void handle(Throwable thrown) {
83// eat the exception without dumping to the console.
84// }
85// }
86
87public class Gatherer {
88
89 static public Hashtable authentications = new Hashtable();
90
91 static final private String SKIN_DEFINITION_FILE = "lib/greenaqua/greenaqua.xml";
92
93 static public boolean always_show_exceptions = true;
94 /** Has the exit flag been set? <i>true</i> if so, <i>false</i> otherwise. */
95 public boolean exit = false;
96 /** The size of the Gatherer window. */
97 public Dimension frame_size = null;
98
99 /** A temporary shared memory area to store HIndexes to speed up metadata.xml writing. */
100 public Hashtable known_indexes = null;
101 /** Legacy copy of the debug_ps. */
102 public PrintStream debug_ps;
103 /** All of the external applications that must exit before we close the Gatherer. */
104 public Vector apps = new Vector();
105 /** Messages that have been issued before we have anyway to show them, ie prior to Log initialization. */
106 public Vector waiting_messages = new Vector();
107 /** The manager in charge of remembering what file extension gets opened with what program. */
108 static public FileAssociationManager assoc_man;
109 /** A public reference to the CollectionManager. */
110 static public CollectionManager c_man;
111 /** A public reference to the Gatherer's configuration. */
112 static public Configuration config;
113 /** a reference to the Servlet Configuration is GS3 */
114 static public ServletConfiguration servlet_config;
115 /** The current modal dialog being shown on screen, if any. */
116 static public ModalDialog current_modal = null;
117 /** A public reference to the Dictionary. */
118 static public Dictionary dictionary;
119 /** A public reference to the FileManager. */
120 static public FileManager f_man;
121 /** A public reference to the GUIManager. */
122 static public GUIManager g_man;
123 /** A static reference to ourselves. */
124 static public Gatherer self;
125 /** The debug print stream. */
126 static public PrintStream debug;
127 /** We are using the GLI for GS3 */
128 static public boolean GS3 = false;
129 /** The name of the necessary environment variable to check for in the programs environment. */
130 static public String KEY = "GSDLPATH";
131
132 // feedback stuff
133 /** is the feedback feature enabled? */
134 static public boolean feedback_enabled = true;
135 /** the action recorder dialog */
136 static public ActionRecorderDialog feedback_dialog = null;
137 /** Extra environment information which must be set before shell processes will run properly. Should always be null if the startup script/program has done its job properly. */
138 static public String extra_env[] = null;
139 private GSDLSiteConfig gsdlsite_cfg = null;
140 private ExternalApplication server = null;
141
142 /** The name of the Gatherers configuration file. */
143 static private String CONFIG_FILE_NAME = "gatherer.cfg";
144
145 /** Magic to allow Enter to fire the default button. */
146 static {
147 KeyStroke enter = KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0);
148 Keymap map = JTextComponent.getKeymap(JTextComponent.DEFAULT_KEYMAP);
149 map.removeKeyStrokeBinding(enter);
150 }
151
152 public Gatherer() {
153 this.self = this;
154 }
155
156 /** Run method. Make the three main modules, c_man, f_man and g_man, and any other necessary classes such as Dictionary.
157 * @param size The desired size of the Gatherer window as a <strong>Dimension</strong>.
158 * @param gsdl_path The path to the gsdl directory, gathered from the startup arguments, and presented as a <strong>String</strong>.
159 * @param gsdl3_path The path to the gsdl3 directory, gathered from the startup arguments, and presented as a <strong>String</strong>.
160 * @param exec_path The path to the library executable, gathered from the startup arguments, and presented as a <strong>String</strong>.
161 * @param debug_enabled <i>true</i> to print verbose debug messages to "debug.txt", <i>false</i> otherwise.
162 * @param perl_path The path to the PERL compiler as a <strong>String</strong>. Necessary for windows platform versions.
163 * @param no_load <i>true</i> to prevent the previously opened collection from reopening.
164 * @param splash A reference to the splash screen.
165 * @param open_collection
166 * @param mirroring_enabled
167 * @param wget_version_str
168 * @param wget_path
169 * @see java.io.FileOutputStream
170 * @see java.io.PrintStream
171 * @see java.lang.Exception
172 * @see java.lang.StringBuffer
173 * @see java.util.Calendar
174 * @see org.greenstone.gatherer.Configuration
175 * @see org.greenstone.gatherer.Dictionary
176 * @see org.greenstone.gatherer.Gatherer.CTRLCHandler
177 * @see org.greenstone.gatherer.GAuthenticator
178 * @see org.greenstone.gatherer.collection.CollectionManager
179 * @see org.greenstone.gatherer.file.FileManager
180 * @see org.greenstone.gatherer.gui.GUIManager
181 * @see org.greenstone.gatherer.gui.Splash
182 */
183 public void run(Dimension size, String gsdl_path, String gsdl3_path, String exec_path, boolean debug_enabled, String perl_path, boolean no_load, Splash splash, String open_collection, String site_name, String servlet_path, boolean mirroring_enabled, String wget_version_str, String wget_path) {
184
185 // This will hopefully catch ctrl-c and terminate, and exit gracefully. However it is platform specific, and may not be supported by some JVMs.
186 /** It does, but I get bloody sick of it working when the Gatherer hangs.
187 CTRLCHandler handler = new CTRLCHandler();
188 Signal.handle(new Signal("INT"), handler);
189 Signal.handle(new Signal("TERM"), handler);
190 handler = null;
191 */
192
193 if (gsdl3_path != null && !gsdl3_path.equals("")) {
194 this.GS3 = true;
195 } else {
196 gsdl3_path = null;
197 }
198 // Create the debug stream only if required.
199 if(debug_enabled) {
200 try {
201 Calendar now = Calendar.getInstance();
202 StringBuffer name = new StringBuffer("debug");
203 name.append(now.get(Calendar.DATE));
204 name.append("-");
205 name.append(now.get(Calendar.MONTH));
206 name.append("-");
207 name.append(now.get(Calendar.YEAR));
208 name.append(".txt");
209 this.debug = new PrintStream(new FileOutputStream(name.toString()));
210 Properties props = System.getProperties();
211 props.list(debug);
212 // Legacy
213 debug_ps = debug;
214 }
215 catch(Exception error) {
216 ///ystem.err.println("Error in Gatherer.init(): " + error);
217 error.printStackTrace();
218 System.exit(1);
219 }
220 }
221 try {
222 // Load Config
223 loadConfig(gsdl_path, gsdl3_path, exec_path, perl_path, mirroring_enabled, wget_version_str, wget_path, site_name);
224
225 // the feedback dialog has been loaded with a default locale,
226 // now set the user specified one
227 if (feedback_enabled && feedback_dialog != null) {
228 feedback_dialog.setLocale(config.getLocale("general.locale", true));
229 }
230
231 // Read Dictionary
232 dictionary = new Dictionary(config.getLocale("general.locale", true), config.getFont("general.font", true));
233
234
235 if (gsdl_path == null) {
236 missingGSDL();
237 }
238
239 // If we were given a server run it if neccessary.
240 if (!GS3 && config.exec_file != null) {
241 startServerEXE();
242 }
243
244 // Having loaded the configuration (necessary to determine if certain warnings have been disabled) and dictionary, we now check if the necessary path variables have been provided.
245 if (config.exec_file == null && config.exec_address == null) {
246 if (config.exec_file == null) {
247 Gatherer.println("config.exec_file is null");
248 }
249 if (config.exec_address == null) {
250 Gatherer.println("config.exec_address is null");
251 }
252 missingEXEC();
253 }
254
255 // Perl path is a little different as it is perfectly ok to start the Gatherer without providing a perl path
256 boolean found_perl = false;
257 if (config.perl_path != null) {
258 // See if the file pointed to actually exists
259 File perl_file = new File(config.perl_path);
260 found_perl = perl_file.exists();
261 perl_file = null;
262 }
263 if (config.perl_path == null || !found_perl) {
264 // Run test to see if we can run perl as is.
265 PerlTest perl_test = new PerlTest();
266 if (perl_test.found()) {
267 // If so replace the perl path with the system default (or null for unix).
268 config.perl_path = perl_test.toString();
269 found_perl = true;
270 }
271 }
272 if (!found_perl) {
273 // Time for an error message.
274 missingPERL();
275 }
276
277 // Size and place the frame on the screen
278 Rectangle bounds = config.getBounds("general.bounds", true);
279 if (bounds == null) {
280 // Choose a sensible default value
281 bounds = new Rectangle(0, 0, 640, 480);
282 }
283
284 // Ensure width and height are reasonable
285 size = bounds.getSize();
286 if (size.width < 640) {
287 size.width = 640;
288 }
289 else if (size.width > config.screen_size.width) {
290 size.width = config.screen_size.width;
291 }
292 if (size.height < 480) {
293 size.height = 480;
294 }
295 else if (size.height > config.screen_size.height) {
296 size.height = config.screen_size.height;
297 }
298 // Set default font
299 setUIFont(config.getFont("general.font", true), config.getFont("general.tooltip_font", true));
300 // Set up proxy
301 setProxy();
302 // Now we set up an Authenticator
303 Authenticator.setDefault(new GAuthenticator());
304
305 assoc_man = new FileAssociationManager();
306 // Create File Manager
307 f_man = new FileManager();
308 // Create Collection Manager
309 c_man = new CollectionManager();
310 // If there was an open collection last session, reopen it.
311
312 if (GS3) {
313 if (site_name==null) {
314 site_name = config.site_name;
315 servlet_path = null; // need to reset this
316 }
317 if (servlet_path == null) {
318 servlet_path = config.getServletPath();
319 }
320 }
321 if(open_collection == null) {
322 open_collection = config.getString("general.open_collection", true);
323 }
324 if(!no_load && open_collection.length() > 0) {
325 c_man.loadCollection(open_collection);
326 }
327 // Create GUI Manager (last) or else suffer the death of a thousand NPE's
328 splash.toFront();
329 g_man = new GUIManager(size);
330 g_man.display();
331
332 // Place the window in the desired location on the screen, if this is do-able (not under most linux window managers apparently. In fact you're lucky if they listen to any of your screen size requests).
333 g_man.setLocation(bounds.x, bounds.y);
334 g_man.setVisible(true);
335
336 // After the window has been made visible, check that it is in the correct place
337 // sometimes java places a window not in the correct place,
338 // but with an offset. If so, we work out what the offset is ]
339 // and change the desired location to take that into account
340 Point location = g_man.getLocation();
341 int x_offset = bounds.x - location.x;
342 int y_offset = bounds.y - location.y;
343 // If not, offset the window to move it into the correct location
344 if (x_offset > 0 || y_offset > 0) {
345 ///ystem.err.println("changing the location to "+(bounds.x + x_offset)+" "+ (bounds.y + y_offset));
346 g_man.setLocation(bounds.x + x_offset, bounds.y + y_offset);
347 }
348
349 // The 'after-display' triggers several events which don't occur until after the visual components are actually available on screen. Examples of these would be the various html renderings, as they can't happen offscreen.
350 g_man.afterDisplay();
351 // Hide the splash.
352 splash.hide();
353 splash.destroy();
354 splash = null;
355 }
356 catch (Exception error) {
357 error.printStackTrace();
358 }
359 }
360
361 /** Exits the Gatherer after ensuring that things needing saving are saved.
362 * @see java.io.FileOutputStream
363 * @see java.io.PrintStream
364 * @see java.lang.Exception
365 * @see javax.swing.JOptionPane
366 * @see org.greenstone.gatherer.Configuration
367 * @see org.greenstone.gatherer.collection.CollectionManager
368 * @see org.greenstone.gatherer.gui.GUIManager
369 */
370 public void exit() {
371 exit = true;
372 // If we have an open collection make note of it.
373 config.setString("general.open_collection", true, null);
374 if(c_man.ready()) {
375 ///ystem.err.println("Collection open.");
376 if(c_man.saved()) {
377 ///ystem.err.println("Collection has been recently saved, so I'll remember it for next time.");
378 config.setString("general.open_collection", true, c_man.getCollectionFilename());
379 }
380 c_man.closeCollection();
381 }
382 if(assoc_man != null) {
383 assoc_man.save();
384 assoc_man = null;
385 }
386
387 // Get the gui to deallocate
388 g_man.hide();
389 g_man.destroy();
390
391 // Store the current position and size (if reasonable) of the Gatherer for next time
392 Rectangle bounds = g_man.getBounds();
393 config.setBounds("general.bounds", true, bounds);
394
395 // Save configuration.
396 saveConfig();
397
398 // Flush dictionary
399 // dictionary.destroy();
400
401 // Flush debug
402 if(debug != null) {
403 try {
404 debug.flush();
405 debug.close();
406 }
407 catch (Exception error) {
408 error.printStackTrace();
409 }
410 }
411
412 // If we started a server, we should try to stop it.
413 if(!GS3 && gsdlsite_cfg != null) {
414 stopServerEXE();
415 }
416
417 if(apps.size() == 0) {
418 System.exit(0);
419 }
420 else {
421 JOptionPane.showMessageDialog(g_man, Dictionary.get("General.Outstanding_Processes"), Dictionary.get("General.Outstanding_Processes_Title"), JOptionPane.ERROR_MESSAGE);
422 g_man.hide();
423 }
424 }
425
426 // used to send new coll messages to the local library
427 public void configServer(String command) {
428
429 try {
430 String raw_url = config.exec_address.toString() + command;
431 URL url = new URL(raw_url);
432 Gatherer.println("Action: " + raw_url);
433 HttpURLConnection library_connection = (HttpURLConnection) url.openConnection();
434 int response_code = library_connection.getResponseCode();
435 if(HttpURLConnection.HTTP_OK <= response_code && response_code < HttpURLConnection.HTTP_MULT_CHOICE) {
436 Gatherer.println("200 - Complete.");
437 }
438 else {
439 Gatherer.println("404 - Failed.");
440 }
441 url = null;
442 }
443 catch(Exception exception) {
444 Gatherer.printStackTrace(exception);
445 ///ystem.err.println("Bad URL.");
446 }
447
448 }
449
450 // used to send reload coll messages to the tomcat server
451 public void configGS3Server(String site, String command) {
452
453 try {
454 // need to add the servlet name to the exec address
455 String raw_url = config.exec_address.toString() + config.getServletPath() + command;
456 URL url = new URL(raw_url);
457 Gatherer.println("Action: " + raw_url);
458 HttpURLConnection library_connection = (HttpURLConnection) url.openConnection();
459 int response_code = library_connection.getResponseCode();
460 if(HttpURLConnection.HTTP_OK <= response_code && response_code < HttpURLConnection.HTTP_MULT_CHOICE) {
461 Gatherer.println("200 - Complete.");
462 }
463 else {
464 Gatherer.println("404 - Failed.");
465 }
466 url = null;
467 }
468 catch(Exception exception) {
469 Gatherer.printStackTrace(exception);
470 ///ystem.err.println("Bad URL.");
471 }
472 }
473
474 /** Retrieve the metadata directory, as required by any MSMCaller implementation.
475 * @return The currently active collection metadata directory as a <strong>String</strong>.
476 * @see org.greenstone.gatherer.collection.CollectionManager
477 */
478 public String getCollectionMetadata() {
479 if (c_man != null && c_man.ready()) {
480 return c_man.getCollectionMetadata();
481 }
482 return "";
483 }
484
485 /** Retrieve a reference to the frame that any dialog boxes will appear relative to, as required by any MSMCaller or CDMCaller implementation.
486 * @return A <strong>JFrame</strong>.
487 * @see org.greenstone.gatherer.gui.GUIManager
488 */
489 /* private JFrame getFrame() {
490 return g_man;
491 } */
492
493 /** Method to retrieve a reference to the metadata set manager class. This is then used to create the 'metadataset' commands in the collection configuration file.
494 * @return A reference to the <Strong>MetadataSetManager</strong>.
495 * @see org.greenstone.gatherer.collection.CollectionManager
496 */
497 /* private MetadataSetManager getMSM() {
498 if(c_man != null && c_man.getCollection() != null && c_man.getCollection().msm != null) {
499 return c_man.getCollection().msm;
500 }
501 return null;
502 } */
503
504 /** Used to 'spawn' a new child application when a file is double clicked.
505 * @param file The file to open
506 * @see org.greenstone.gatherer.Gatherer.ExternalApplication
507 */
508 public void spawnApplication(File file) {
509 String [] commands = assoc_man.getCommand(file);
510 if(commands != null) {
511 ExternalApplication app = new ExternalApplication(commands);
512 apps.add(app);
513 app.start();
514 }
515 else {
516 ///ystem.err.println("No open command available.");
517 }
518 }
519
520 /** Used to 'spawn' a new browser application or reset an existing one when the preview button is clicked
521 * @param url The url to open the browser at
522 * @see org.greenstone.gatherer.Gatherer.BrowserApplication
523 */
524 public void spawnBrowser(String url) {
525 String command = assoc_man.getBrowserCommand(url);
526 if (command != null) {
527 BrowserApplication app = new BrowserApplication(command, url);
528 apps.add(app);
529 app.start();
530 }
531 else {
532 ///ystem.err.println("No browser command available.");
533 }
534 }
535
536 /** The entry point into the Gatherer. Parses arguments.
537 * @param args A collection of arguments that may include: initial screen size, dictionary, path to the GSDL etc.
538 * @see java.io.File
539 * @see java.io.FileInputStream
540 * @see java.lang.Exception
541 * @see java.util.Properties
542 * @see org.greenstone.gatherer.Dictionary
543 * @see org.greenstone.gatherer.Gatherer
544 * @see org.greenstone.gatherer.gui.Splash
545 * @see org.greenstone.gatherer.util.StaticStrings
546 */
547 static public void main(String[] args) {
548 // A serious hack, but its good enough to stop crappy 'Could not lock user prefs' error messages.
549 // Thanks to Walter Schatz from the java forums.
550 System.setProperty("java.util.prefs.syncInterval","2000000"); // One message every 600 hours!
551
552
553 // Override the exception handler with a new one which we can easily quiet the exceptions from.
554 // System.setProperty("sun.awt.exception.handler", "GLIExceptionHandler");
555
556 // Ensure platform specific LAF
557 try {
558 UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
559 }
560 catch(Exception exception) {
561 exception.printStackTrace();
562 }
563
564 Gatherer gatherer = new Gatherer();
565 boolean debug = false;
566 boolean feedback_enabled = false;
567 boolean mirroring_enabled = false;
568 boolean no_load = false;
569 Dictionary dictionary = new Dictionary(null, null); // Default dictionary. Only used for starting error messages.
570 Dimension size = new Dimension(800, 540);
571 String exec_path = null;
572 String extra = null;
573 String filename = null;
574 String gsdl_path = null;
575 String gsdl3_path = null;
576 String perl_path = null;
577 String site_name = null; // for GS3
578 String servlet_path = null;
579 String wget_path = null;
580 String wget_version_str = StaticStrings.NO_WGET_STR;
581 // Parse arguments
582 int argument_index = 0;
583 String next_token = null;
584 while(argument_index < args.length || next_token != null) {
585 // 1. We start by attempting to parse an argument name. An argument must start with a '-', and should not contain spaces. If anything else is encountered it is ignored.
586 String argument_name = null;
587 if(next_token == null) {
588 next_token = args[argument_index];
589 argument_index++;
590 }
591 if(next_token.startsWith(StaticStrings.MINUS_CHARACTER)) {
592 // Trim second '-' just to be kind to Unixy-type people
593 if(next_token.startsWith(StaticStrings.MINUS_CHARACTER + StaticStrings.MINUS_CHARACTER)) {
594 argument_name = next_token.substring(1);
595 }
596 else {
597 argument_name = next_token;
598 }
599 }
600 next_token = null;
601 // 2. If we now have an argument name we continue by attempting to parse a value. A value is taken to be the sequence of space seperated Strings between the last argument name and up to but not including the next argument name. Of course an argument needn't have any value (ie -debug, -help), in which case value will be null.
602 if(argument_name != null) {
603 String argument_value = null;
604 StringBuffer argument_value_buffer = new StringBuffer("");
605 while(argument_index < args.length && next_token == null) {
606 next_token = args[argument_index];
607 argument_index++;
608 // If we just parsed an arbitary String then append it to value, followed by a single space
609 if(!next_token.startsWith(StaticStrings.MINUS_CHARACTER)) {
610 argument_value_buffer.append(next_token);
611 argument_value_buffer.append(StaticStrings.SPACE_CHARACTER);
612 next_token = null;
613 }
614 // If the argument token retrieved is an argument name, then leave it in next_token, which will cause the argument parsing process to move onto the next step.
615 }
616 // If a value now exists in argument buffer, retrieve it. Remove the last character as it will be an erroneous space.
617 if(argument_value_buffer.length() > 0) {
618 argument_value = argument_value_buffer.substring(0, argument_value_buffer.length() - 1);
619 }
620
621 // 3. We now have the argument name, and any associated value. We are ready to store the data in the appropriate variables.
622 Gatherer.println("Parsed Argument: name=" + argument_name + (argument_value != null ? (", value=" + argument_value) : ", no value"));
623 // 3a. First those arguments that have no associated value
624 if(argument_value == null) {
625 if(argument_name.equals(StaticStrings.HELP_ARGUMENT)) {
626 printUsage(dictionary);
627 System.exit(0);
628 }
629 // Run GLI in debug mode. Produces debug log plus extra messages.
630 else if(argument_name.equals(StaticStrings.DEBUG_ARGUMENT)) {
631 debug = true;
632 }
633 // Run GLI with feedback enabled.
634 else if(argument_name.equals(StaticStrings.FEEDBACK_ARGUMENT)) {
635 feedback_enabled = true;
636 }
637 // Forces no loading on previous collection.
638 else if(argument_name.equals(StaticStrings.NO_LOAD_ARGUMENT)) {
639 no_load = true;
640 filename = null;
641 }
642 // Run the GLI with mirroring enabled
643 else if(argument_name.equals(StaticStrings.MIRROR_ARGUMENT)) {
644 mirroring_enabled = true;
645 }
646 /* I've got a sneak suspicion Aqua look and feel is not free domain
647 // Specify the use of Greenstone LAF.
648 else if(argument_name.equals(StaticStrings.SKIN_ARGUMENT)) {
649 // SkinLF
650 try {
651 SkinLookAndFeel.setSkin(SkinLookAndFeel.loadThemePackDefinition(SkinUtils.toURL(new File(SKIN_DEFINITION_FILE))));
652 SkinLookAndFeel.enable();
653 }
654 catch (Exception error) {
655 ///ystem.err.println("Error: " + error);
656 error.printStackTrace();
657 }
658 }
659 */
660 }
661 // 3b. Now for those that do
662 else {
663 // Parse the path to the GSDL. Required argument.
664 if(argument_name.equals(StaticStrings.GSDL_ARGUMENT)) {
665 if(argument_value.endsWith(File.separator)) {
666 gsdl_path = argument_value;
667 }
668 else {
669 gsdl_path = argument_value + File.separator;
670 }
671 }
672 // GSDL3 path
673 if(argument_name.equals(StaticStrings.GSDL3_ARGUMENT)) {
674 if(argument_value.endsWith(File.separator)) {
675 gsdl3_path = argument_value;
676 }
677 else {
678 gsdl3_path = argument_value + File.separator;
679 }
680 }
681 else if (argument_name.equals(StaticStrings.SITE_ARGUMENT)) {
682 site_name = argument_value;
683 }
684 else if (argument_name.equals(StaticStrings.SERVLET_ARGUMENT)) {
685 servlet_path = argument_value;
686 }
687 // Specify a collection to load initially. Could be used for file associations.
688 else if(argument_name.equals(StaticStrings.LOAD_ARGUMENT)) {
689 filename = argument_value;
690 no_load = false;
691 }
692 // Parse the url to a running web server, or a file path to the local library server.
693 else if(argument_name.equals(StaticStrings.LIBRARY_ARGUMENT)) {
694 exec_path = argument_value;
695 // If there is no colon in first five characters of the exec_path (which would either be an existing protocol, or a windows file path to say a local library), we can append the protocol http://.
696 if(argument_value.lastIndexOf(StaticStrings.COLON_CHARACTER, 5) == -1) {
697 exec_path = StaticStrings.HTTP_PROTOCOL_STR + argument_value;
698 }
699 else {
700 exec_path = argument_value;
701 }
702 // If the user has given us an address, but it ends with a '/' we assume we're using the greenstone library.cgi
703 if(exec_path.startsWith(StaticStrings.HTTP_PROTOCOL_STR) && exec_path.endsWith(StaticStrings.URL_SEPARATOR_CHARACTER)) {
704 exec_path = exec_path + StaticStrings.LIBRARY_STR;
705 }
706 }
707 // Parse the path to PERL. If not provided its assumes perl should be availble on the PATH.
708 else if(argument_name.equals(StaticStrings.PERL_ARGUMENT)) {
709 perl_path = argument_value;
710 // Test whether this points to the Perl bin directory or the Perl executable itself.
711 File perl_file = new File(perl_path);
712 if(perl_file.isDirectory()) {
713 // If this is windows we create a child file perl.exe, otherwise we create perl
714 if(Utility.isWindows()) {
715 perl_file = new File(perl_file, Utility.PERL_EXECUTABLE_WINDOWS);
716 }
717 else {
718 perl_file = new File(perl_file, Utility.PERL_EXECUTABLE_UNIX);
719 }
720 // And store this new path.
721 perl_path = perl_file.getAbsolutePath();
722 perl_file = null;
723 }
724 // Otherwise its fine as it is
725 }
726 // Test for the presence of a WGet version. This is only useful if the user has enabled mirroring. Note that mirroring can be enabled by running the GLI with -mirror, editing the config.xml for GLI, or through a new option on the connections page of the preferences.
727 else if(argument_name.equals(StaticStrings.WGET_ARGUMENT)) {
728 if(argument_value.startsWith(StaticStrings.WGET_STR)) {
729 wget_version_str = StaticStrings.WGET_STR;
730 wget_path = argument_value.substring(StaticStrings.WGET_STR.length());
731 }
732 else if(argument_value.startsWith(StaticStrings.WGET_OLD_STR)) {
733 wget_version_str = StaticStrings.WGET_OLD_STR;
734 wget_path = argument_value.substring(StaticStrings.WGET_OLD_STR.length());
735 }
736 }
737 }
738 }
739 // Argument name was null, nothing to be done.
740 }
741 next_token = null;
742 // Arguments all parsed.
743
744 if (feedback_enabled) {
745 // if feedback is enabled, set up the recorder dialog
746 Locale currLocale = Locale.getDefault(); // use teh default locale for now - this will be changed by the Gatherer run method
747 ActionRecorderDialog dlg = new ActionRecorderDialog (currLocale);
748 gatherer.feedback_enabled = true;
749 gatherer.feedback_dialog = dlg;
750 }
751
752 // Splash screen.
753 Splash splash = new Splash();
754 // dictionary.destroy();
755 gatherer.run(size, gsdl_path, gsdl3_path, exec_path, debug, perl_path, no_load, splash, filename, site_name, servlet_path, mirroring_enabled, wget_version_str, wget_path);
756 }
757
758 /** Prints a warning message about a missing library path, which means the final collection cannot be previewed in the Gatherer.
759 */
760 static public void missingEXEC() {
761 WarningDialog dialog = new WarningDialog("warning.MissingEXEC", "general.exec_address");
762 dialog.setValueField(new URLField(Gatherer.config.getColor("coloring.editable_foreground", false), Gatherer.config.getColor("coloring.editable_background", false), Gatherer.config.getColor("coloring.error_foreground", false), Gatherer.config.getColor("coloring.error_background", false)));
763 dialog.display();
764 dialog.dispose();
765 dialog = null;
766
767 String library_path_string = Gatherer.config.getString("general.exec_address", true);
768 if (!library_path_string.equals("")) {
769 try {
770 Gatherer.config.exec_address = new URL(library_path_string);
771 }
772 catch (MalformedURLException error) {
773 ///ystem.err.println("Error: Bad address: " + exec_address_string);
774 }
775 }
776 }
777
778 /** Prints a warning message about a missing GSDL path, which although not fatal pretty much ensures nothing will work properly in the Gatherer.
779 */
780 static public void missingGSDL() {
781 WarningDialog dialog = new WarningDialog("warning.MissingGSDL", false);
782 dialog.display();
783 dialog.dispose();
784 dialog = null;
785 }
786
787 /** Prints a warning message about a missing PERL path, which although not fatal pretty much ensures no collection creation/building will work properly in the Gatherer. */
788 static public void missingPERL() {
789 WarningDialog dialog = new WarningDialog("warning.MissingPERL", false);
790 dialog.display();
791 dialog.dispose();
792 dialog = null;
793 }
794
795 /** Print a message to the debug stream. */
796 static synchronized public void print(String message) {
797 if(debug != null) {
798 debug.print(message);
799 System.err.print(message);
800 }
801 }
802 /** Print a message to the debug stream. */
803 static synchronized public void println(String message) {
804 if(debug != null) {
805 debug.println(message);
806 System.err.println(message);
807 }
808 }
809
810 /** Print a stack trace to the debug stream. */
811 static synchronized public void printStackTrace(Exception exception) {
812 if(debug != null) {
813 exception.printStackTrace(debug);
814 exception.printStackTrace();
815 }
816 }
817 /** Prints a usage message to screen.
818 */
819 static public void printUsage(Dictionary dictionary) {
820 System.out.println(Dictionary.get("General.Usage"));
821 }
822
823 /** Sets up the proxy connection by setting JVM Environment flags and creating a new Authenticator.
824 * @see java.lang.Exception
825 * @see java.lang.System
826 * @see java.net.Authenticator
827 * @see org.greenstone.gatherer.Configuration
828 * @see org.greenstone.gatherer.GAuthenticator
829 */
830 static public void setProxy() {
831 try {// Can throw several exceptions
832 if(Gatherer.config.get("general.use_proxy", true)) {
833 System.setProperty("http.proxyType", "4");
834 System.setProperty("http.proxyHost", Gatherer.config.getString("general.proxy_host", true));
835 System.setProperty("http.proxyPort", Gatherer.config.getString("general.proxy_port", true));
836 System.setProperty("http.proxySet", "true");
837 } else {
838 System.setProperty("http.proxySet", "false");
839 }
840 } catch (Exception error) {
841 Gatherer.println("Error in Gatherer.initProxy(): " + error);
842 Gatherer.printStackTrace(error);
843 }
844 }
845
846 /** Set all the default fonts of the program to a choosen font, except the tooltip font which should be some fixed width font.
847 * @param f The default font to use in the Gatherer as a <strong>FontUIResource</strong>.
848 * @param ttf The tooltip font to use also as a <strong>FontUIResource</strong>.
849 * @see java.util.Enumeration
850 * @see javax.swing.UIManager
851 */
852 static private void setUIFont(FontUIResource f, FontUIResource ttf) {
853 // sets the default font for all Swing components.
854 // ex.
855 // setUIFont (new FontUIResource("Serif",Font.ITALIC,12));
856 //
857 Enumeration keys = UIManager.getDefaults().keys();
858 while (keys.hasMoreElements()) {
859 Object key = keys.nextElement();
860 Object value = UIManager.get (key);
861 if (value instanceof FontUIResource)
862 UIManager.put (key, f);
863 }
864 // Now set the tooltip font to some fixed width font
865 UIManager.put("ToolTip.font", ttf);
866 }
867
868 /** Loads the configuration file if one exists. Otherwise it creates a new one. Currently uses serialization.
869 * @param gsdl_path The path to the gsdl directory, gathered from the startup arguments, and presented as a <strong>String</strong>.
870 * @param exec_path The path to the library executable, gathered from the startup arguments, and presented as a <strong>String</strong>.
871 * @param perl_path The path to the PERL compiler as a <strong>String</strong>. Necessary for windows platform versions.
872 * @param mirroring_enabled
873 * @param wget_version_str
874 * @param wget_path
875 * @see java.io.FileInputStream
876 * @see java.io.ObjectInputStream
877 * @see java.lang.Exception
878 * @see org.greenstone.gatherer.Configuration
879 */
880 private void loadConfig(String gsdl_path, String gsdl3_path, String exec_path, String perl_path, boolean mirroring_enabled, String wget_version_str, String wget_path, String site_name) {
881 try {
882 config = new Configuration(gsdl_path, gsdl3_path, exec_path, perl_path, mirroring_enabled, wget_version_str, wget_path, site_name);
883 }
884 catch (Exception error) {
885 Gatherer.println("config.xml is not a well formed XML document.");
886 Gatherer.printStackTrace(error);
887 }
888 if (GS3) {
889 try {
890 servlet_config = new ServletConfiguration(gsdl3_path);
891 } catch (Exception error) {
892 error.printStackTrace();
893 }
894 }
895
896 }
897
898 /** Causes the general configuration file to export itself to xml. Doesn't effect any remaining collection configuration, as its up to the collection manager to handle them (especially since we have no idea where they are going). */
899 private void saveConfig() {
900 try {
901 config.save();
902 } catch (Exception error) {
903 Gatherer.printStackTrace(error);
904 }
905 }
906
907 private void startServerEXE() {
908 if(config.exec_file != null && config.exec_address == null && Utility.isWindows() && !GS3) {
909 // First of all we create a GSDLSiteCFG object and check if a URL is already present, in which case the server is already running.
910 gsdlsite_cfg = new GSDLSiteConfig(config.exec_file);
911 String url = gsdlsite_cfg.getURL();
912 // If its already running then set exec address.
913 if(url != null) {
914 try {
915 config.exec_address = new URL(url);
916 }
917 catch(Exception error) {
918 }
919 }
920 // Otherwise its time to run the server in a spawned process.
921 if(config.exec_address == null && config.exec_file.exists()) {
922 // Configure for immediate entry. Note that this only works if the gsdlsite.cfg file exists.
923 gsdlsite_cfg.set();
924 // Spawn server
925 String command = config.exec_file.getAbsolutePath() + " " + gsdlsite_cfg.getSiteConfigFilename();
926 server = new ExternalApplication(command);
927 server.start();
928 command = null;
929 // Now we have to wait until program has started. We do this by reloading and checking
930 ///ystem.err.print("Waiting until the local library has loaded");
931 try {
932 gsdlsite_cfg.load();
933
934 int try_again = JOptionPane.YES_OPTION;
935 int attempt_count = 0;
936 while(gsdlsite_cfg.getURL() == null && try_again == JOptionPane.YES_OPTION) {
937 ///ystem.err.print(".");
938 if(attempt_count == 60) {
939 attempt_count = 0;
940 try_again = JOptionPane.showConfirmDialog(Gatherer.g_man, Dictionary.get("Server.QuitTimeOut"), Dictionary.get("General.Warning"), JOptionPane.YES_NO_OPTION);
941 }
942 else {
943 synchronized(this) {
944 wait(1000); // Wait one second (give or take)
945 }
946 gsdlsite_cfg.load();
947 attempt_count++;
948 }
949 }
950
951 if((url = gsdlsite_cfg.getURL()) != null) {
952 // Ta-da. Now the url should be available.
953 config.exec_address = new URL(url);
954
955 // A quick test involves opening a connection to get the home page for this collection. If this fails then we try changing the url to be localhost.
956 try {
957 Gatherer.println("Try connecting to server on config url: '" + config.exec_address + "'");
958 URLConnection connection = config.exec_address.openConnection();
959 connection.getContent();
960 }
961 catch(IOException bad_url_connection) {
962 try {
963 Gatherer.println("Try connecting to server on local host: '" + gsdlsite_cfg.getLocalHostURL() + "'");
964 config.exec_address = new URL(gsdlsite_cfg.getLocalHostURL ());
965 URLConnection connection = config.exec_address.openConnection();
966 connection.getContent();
967 }
968 catch(IOException worse_url_connection) {
969 Gatherer.println("Can't connect to server on either address.");
970 config.exec_address = null;
971 config.exec_file = null;
972 }
973 }
974 }
975 // Unable to start local library. Show appropriate message.
976 else {
977 missingEXEC();
978 }
979 }
980 catch (Exception error) {
981 error.printStackTrace();
982 }
983 }
984 // Can't do a damb thing.
985 }
986 Gatherer.println("Having started server.exe, exec_address is: " + config.exec_address);
987 }
988
989 private void stopServerEXE() {
990 if(server != null && config.exec_address != null) {
991 // See if its already exited for some reason.
992 gsdlsite_cfg.load();
993 if(gsdlsite_cfg.getURL() != null) {
994 // Send the command for it to exit.
995 configServer(GSDLSiteConfig.QUIT_COMMAND);
996 // Wait until it exits.
997 try {
998 gsdlsite_cfg.load();
999 int try_again = JOptionPane.YES_OPTION;
1000 int attempt_count = 0;
1001 while(gsdlsite_cfg.getURL() != null && try_again == JOptionPane.YES_OPTION) {
1002 if(attempt_count == 60) {
1003 attempt_count = 0;
1004 try_again = JOptionPane.showConfirmDialog(Gatherer.g_man, Dictionary.get("Server.QuitTimeOut"), Dictionary.get("General.Warning"), JOptionPane.YES_NO_OPTION);
1005 }
1006 else {
1007 synchronized(this) {
1008 wait(1000); // Wait one second (give or take)
1009 }
1010 gsdlsite_cfg.load();
1011 attempt_count++;
1012 }
1013 }
1014 //if(gsdlsite_cfg.getURL() != null) {
1015 //JOptionPane.showMessageDialog(Gatherer.g_man, Dictionary.get("Server.QuitManual"), Dictionary.get("General.Error"), JOptionPane.ERROR_MESSAGE);
1016 //}
1017 }
1018 catch (Exception error) {
1019 error.printStackTrace();
1020 }
1021 }
1022 // Restore the gsdlsite_cfg.
1023 if(gsdlsite_cfg != null) {
1024 gsdlsite_cfg.restore();
1025 }
1026 // If the local server is still running then our changed values will get overwritten.
1027 if(gsdlsite_cfg.getURL() != null) {
1028 JOptionPane.showMessageDialog(Gatherer.g_man, Dictionary.get("Server.QuitFailed"), Dictionary.get("General.Error"), JOptionPane.ERROR_MESSAGE);
1029 }
1030 gsdlsite_cfg = null;
1031 server = null;
1032 }
1033 }
1034
1035 /** This private class contains an instance of an external application running within a JVM shell. It is important that this process sits in its own thread, but its more important that when we exit the Gatherer we don't actually System.exit(0) the Gatherer object until the user has volunteerily ended all of these child processes. Otherwise when we quit the Gatherer any changes the users may have made in external programs will be lost and the child processes are automatically deallocated. */
1036 private class ExternalApplication
1037 extends Thread {
1038 private Process process = null;
1039 /** The initial command string given to this sub-process. */
1040 private String command = null;
1041 private String[] commands = null;
1042 /** Constructor.
1043 * @param command The initial command <strong>String</strong>.
1044 */
1045 public ExternalApplication(String command) {
1046 this.command = command;
1047 }
1048
1049 public ExternalApplication(String[] commands) {
1050 this.commands = commands;
1051 }
1052 /** We start the child process inside a new thread so it doesn't block the rest of Gatherer.
1053 * @see java.lang.Exception
1054 * @see java.lang.Process
1055 * @see java.lang.Runtime
1056 * @see java.lang.System
1057 * @see java.util.Vector
1058 */
1059 public void run() {
1060 // Call an external process using the args.
1061 try {
1062 if(commands != null) {
1063 StringBuffer whole_command = new StringBuffer();
1064 for(int i = 0; i < commands.length; i++) {
1065 whole_command.append(commands[i]);
1066 whole_command.append(" ");
1067 }
1068 println("Running " + whole_command.toString());
1069 Runtime rt = Runtime.getRuntime();
1070 process = rt.exec(commands);
1071 process.waitFor();
1072 }
1073 else {
1074 println("Running " + command);
1075 Runtime rt = Runtime.getRuntime();
1076 process = rt.exec(command);
1077 process.waitFor();
1078 }
1079 }
1080 catch (Exception error) {
1081 println("Error in ExternalApplication.run(): " + error);
1082 printStackTrace(error);
1083 }
1084 // Remove ourself from Gatherer list of threads.
1085 apps.remove(this);
1086 // Call exit if we were the last outstanding child process thread.
1087 if(apps.size() == 0 && exit == true) {
1088 stopServerEXE();
1089 System.exit(0);
1090 }
1091 }
1092 public void stopExternalApplication() {
1093 if(process != null) {
1094 process.destroy();
1095 }
1096 }
1097 }
1098 /** This private class contains an instance of an external application running within a JVM shell. It is important that this process sits in its own thread, but its more important that when we exit the Gatherer we don't actually System.exit(0) the Gatherer object until the user has volunteerily ended all of these child processes. Otherwise when we quit the Gatherer any changes the users may have made in external programs will be lost and the child processes are automatically deallocated. */
1099 private class BrowserApplication
1100 extends Thread {
1101 private Process process = null;
1102 /** The initial command string given to this sub-process. */
1103 private String command = null;
1104 private String url = null;
1105 private String[] commands = null;
1106 /** Constructor.
1107 * @param command The initial command <strong>String</strong>.
1108 */
1109// public BrowserApplication(String command) {
1110// this.command = command;
1111// }
1112
1113 public BrowserApplication(String command, String url) {
1114 StringTokenizer st = new StringTokenizer(command);
1115 int num_tokens = st.countTokens();
1116 this.commands = new String [num_tokens];
1117 int i=0;
1118 while (st.hasMoreTokens()) {
1119 commands[i] = st.nextToken();
1120 i++;
1121 }
1122 //this.commands = commands;
1123 this.url = url;
1124 }
1125 /** We start the child process inside a new thread so it doesn't block the rest of Gatherer.
1126 * @see java.lang.Exception
1127 * @see java.lang.Process
1128 * @see java.lang.Runtime
1129 * @see java.lang.System
1130 * @see java.util.Vector
1131 */
1132 public void run() {
1133 // Call an external process using the args.
1134 if(commands == null) {
1135 apps.remove(this);
1136 return;
1137 }
1138 try {
1139 String prog_name = commands[0];
1140 String lower_name = prog_name.toLowerCase();
1141 if (lower_name.indexOf("mozilla") != -1 || lower_name.indexOf("netscape") != -1) {
1142 Gatherer.println("found mozilla or netscape, trying remote it");
1143 // mozilla and netscape, try using a remote command to get things in the same window
1144 String [] new_commands = new String[] {prog_name, "-raise", "-remote", "openURL("+url+")"};
1145 if(debug != null) {
1146 printArray(new_commands);
1147 }
1148 Runtime rt = Runtime.getRuntime();
1149 process = rt.exec(new_commands);
1150 int exitCode = process.waitFor();
1151 if (exitCode != 0) { // if Netscape or mozilla was not open
1152 Gatherer.println("couldn't do remote, trying original command");
1153 if(debug != null) {
1154 printArray(commands);
1155 }
1156 process = rt.exec(commands); // try the original command
1157 }
1158 } else {
1159 // just run what we have been given
1160 StringBuffer whole_command = new StringBuffer();
1161 for(int i = 0; i < commands.length; i++) {
1162 whole_command.append(commands[i]);
1163 whole_command.append(" ");
1164 }
1165 println("Running " + whole_command.toString());
1166 Runtime rt = Runtime.getRuntime();
1167 process = rt.exec(commands);
1168 process.waitFor();
1169 }
1170 }
1171
1172 catch (Exception error) {
1173 println("Error in BrowserApplication.run(): " + error);
1174 printStackTrace(error);
1175 }
1176 // Remove ourself from Gatherer list of threads.
1177 apps.remove(this);
1178 // Call exit if we were the last outstanding child process thread.
1179 if(apps.size() == 0 && exit == true) {
1180 stopServerEXE();
1181 System.exit(0);
1182 }
1183 }
1184 public void printArray(String [] array) {
1185 for(int i = 0; i < array.length; i++) {
1186 debug.print(array[i]+" ");
1187 System.err.println(array[i]+" ");
1188 }
1189 }
1190 public void stopBrowserApplication() {
1191 if(process != null) {
1192 process.destroy();
1193 }
1194 }
1195 }
1196
1197 /** This class is intented to detect a specific SIGNAL, in this case SIGINT, and exit properly, rather than letting the Gatherer be interrupted which has the potential to leave erroneous lock files. */
1198 private class CTRLCHandler
1199 implements SignalHandler {
1200 /** <i>true</i> if we ignore any other signals we receive, most likely because we are already dealing with one, <i>false</i> otherwise. */
1201 private boolean ignore = false;
1202 /** The method called by the system to inform us a signal has occured.
1203 * @param sig The <strong>Signal</strong> itself.
1204 * @see org.greenstone.gatherer.collection.CollectionManager
1205 */
1206 public void handle(Signal sig) {
1207 if(!ignore) {
1208 ignore = true;
1209 // handle SIGINT
1210 System.out.println("Caught Ctrl-C...");
1211 if(c_man != null && c_man.ready()) {
1212 c_man.closeCollection();
1213 }
1214 exit();
1215 ignore = false;
1216 }
1217 }
1218 }
1219
1220 private class PerlTest {
1221
1222 private String[] command = new String[2];
1223
1224 public PerlTest() {
1225 if(Utility.isWindows()) {
1226 command[0] = Utility.PERL_EXECUTABLE_WINDOWS;
1227 }
1228 else {
1229 command[0] = Utility.PERL_EXECUTABLE_UNIX;
1230 }
1231 command[1] = "-version";
1232 }
1233
1234 public boolean found() {
1235 boolean found = false;
1236 try {
1237 Process prcs = Runtime.getRuntime().exec(command);
1238 prcs.waitFor();
1239 found = (prcs.exitValue() == 0);
1240 prcs = null;
1241 }
1242 catch(Exception error) {
1243 }
1244 return found;
1245 }
1246
1247 public String toString() {
1248 return command[0];
1249 }
1250 }
1251
1252 static public class GLIExceptionHandler {
1253 public void handle(Throwable thrown) {
1254 if(always_show_exceptions || debug != null) {
1255 thrown.printStackTrace();
1256 }
1257 }
1258 }
1259}
Note: See TracBrowser for help on using the repository browser.