added svn:ignores
[debian/openrocket] / src / net / sf / openrocket / startup / Startup.java
1 package net.sf.openrocket.startup;
2
3 import java.awt.GraphicsEnvironment;
4 import java.awt.event.ActionEvent;
5 import java.awt.event.ActionListener;
6 import java.io.File;
7 import java.io.PrintStream;
8 import java.util.List;
9 import java.util.Locale;
10 import java.util.concurrent.atomic.AtomicInteger;
11
12 import javax.swing.JOptionPane;
13 import javax.swing.SwingUtilities;
14 import javax.swing.Timer;
15 import javax.swing.ToolTipManager;
16
17 import net.sf.openrocket.communication.UpdateInfo;
18 import net.sf.openrocket.communication.UpdateInfoRetriever;
19 import net.sf.openrocket.database.Databases;
20 import net.sf.openrocket.database.ThrustCurveMotorSet;
21 import net.sf.openrocket.database.ThrustCurveMotorSetDatabase;
22 import net.sf.openrocket.file.iterator.DirectoryIterator;
23 import net.sf.openrocket.file.iterator.FileIterator;
24 import net.sf.openrocket.file.motor.MotorLoaderHelper;
25 import net.sf.openrocket.gui.dialogs.UpdateInfoDialog;
26 import net.sf.openrocket.gui.main.BasicFrame;
27 import net.sf.openrocket.gui.main.ExceptionHandler;
28 import net.sf.openrocket.gui.main.SimpleFileFilter;
29 import net.sf.openrocket.gui.main.Splash;
30 import net.sf.openrocket.l10n.DebugTranslator;
31 import net.sf.openrocket.l10n.ResourceBundleTranslator;
32 import net.sf.openrocket.l10n.Translator;
33 import net.sf.openrocket.logging.DelegatorLogger;
34 import net.sf.openrocket.logging.LogHelper;
35 import net.sf.openrocket.logging.LogLevel;
36 import net.sf.openrocket.logging.LogLevelBufferLogger;
37 import net.sf.openrocket.logging.PrintStreamLogger;
38 import net.sf.openrocket.motor.Motor;
39 import net.sf.openrocket.motor.ThrustCurveMotor;
40 import net.sf.openrocket.util.GUIUtil;
41 import net.sf.openrocket.util.Prefs;
42
43
44 /**
45  * A startup class that checks that a suitable JRE environment is being run.
46  * If the environment is too old the execution is canceled, and if OpenJDK is being
47  * used warns the user of problems and confirms whether to continue.
48  * 
49  * @author Sampo Niskanen <sampo.niskanen@iki.fi>
50  */
51 public class Startup {
52         
53         private static LogHelper log;
54         
55         private static final String LOG_STDERR_PROPERTY = "openrocket.log.stderr";
56         private static final String LOG_STDOUT_PROPERTY = "openrocket.log.stdout";
57         
58         private static final int LOG_BUFFER_LENGTH = 50;
59         
60         private static final String THRUSTCURVE_DIRECTORY = "datafiles/thrustcurves/";
61         
62
63         /** Block motor loading for this many milliseconds */
64         private static AtomicInteger blockLoading = new AtomicInteger(Integer.MAX_VALUE);
65         
66         
67         public static void main(final String[] args) throws Exception {
68                 
69                 // Check for "openrocket.debug" property before anything else
70                 checkDebugStatus();
71                 
72                 // Initialize logging first so we can use it
73                 initializeLogging();
74                 
75                 // Setup the translations
76                 initializeL10n();
77                 
78                 // Check that we have a head
79                 checkHead();
80                 
81                 // Check that we're running a good version of a JRE
82                 log.info("Checking JRE compatibility");
83                 VersionHelper.checkVersion();
84                 VersionHelper.checkOpenJDK();
85                 
86                 // Run the actual startup method in the EDT since it can use progress dialogs etc.
87                 log.info("Running main");
88                 SwingUtilities.invokeAndWait(new Runnable() {
89                         @Override
90                         public void run() {
91                                 runMain(args);
92                         }
93                 });
94                 
95                 log.info("Startup complete");
96                 
97                 // Block motor loading for 1.5 seconds to allow window painting
98                 blockLoading.set(1500);
99         }
100         
101         
102
103
104
105         private static void checkDebugStatus() {
106                 if (System.getProperty("openrocket.debug") != null) {
107                         setPropertyIfNotSet("openrocket.log.stdout", "VBOSE");
108                         setPropertyIfNotSet("openrocket.log.tracelevel", "VBOSE");
109                         setPropertyIfNotSet("openrocket.debug.menu", "true");
110                         setPropertyIfNotSet("openrocket.debug.mutexlocation", "true");
111                         setPropertyIfNotSet("openrocket.debug.motordigest", "true");
112                 }
113         }
114         
115         private static void setPropertyIfNotSet(String key, String value) {
116                 if (System.getProperty(key) == null) {
117                         System.setProperty(key, value);
118                 }
119         }
120         
121         
122         /**
123          * Initializes the localization system.
124          */
125         private static void initializeL10n() {
126                 String locale = System.getProperty("openrocket.locale");
127                 if (locale != null) {
128                         Locale l;
129                         String[] split = locale.split("[_-]", 3);
130                         if (split.length == 1) {
131                                 l = new Locale(split[0]);
132                         } else if (split.length == 2) {
133                                 l = new Locale(split[0], split[1]);
134                         } else {
135                                 l = new Locale(split[0], split[1], split[2]);
136                         }
137                         log.info("Setting custom locale " + l);
138                         Locale.setDefault(l);
139                 }
140                 
141                 Translator t;
142                 if (Locale.getDefault().getLanguage().equals("xx")) {
143                         t = new DebugTranslator();
144                 } else {
145                         t = new ResourceBundleTranslator("l10n.messages");
146                 }
147                 
148                 log.info("Set up translation for locale " + Locale.getDefault() +
149                                 ", debug.currentFile=" + t.get("debug.currentFile"));
150                 
151                 Application.setBaseTranslator(t);
152         }
153         
154         
155
156         private static void runMain(String[] args) {
157                 
158                 // Initialize the splash screen with version info
159                 log.info("Initializing the splash screen");
160                 Splash.init();
161                 
162                 // Setup the uncaught exception handler
163                 log.info("Registering exception handler");
164                 ExceptionHandler.registerExceptionHandler();
165                 
166                 // Start update info fetching
167                 final UpdateInfoRetriever updateInfo;
168                 if (Prefs.getCheckUpdates()) {
169                         log.info("Starting update check");
170                         updateInfo = new UpdateInfoRetriever();
171                         updateInfo.start();
172                 } else {
173                         log.info("Update check disabled");
174                         updateInfo = null;
175                 }
176                 
177                 // Set the best available look-and-feel
178                 log.info("Setting best LAF");
179                 GUIUtil.setBestLAF();
180                 
181                 // Set tooltip delay time.  Tooltips are used in MotorChooserDialog extensively.
182                 ToolTipManager.sharedInstance().setDismissDelay(30000);
183                 
184                 // Load defaults
185                 Prefs.loadDefaultUnits();
186                 
187                 // Load motors etc.
188                 log.info("Loading databases");
189                 loadMotor();
190                 Databases.fakeMethod();
191                 
192                 // Starting action (load files or open new document)
193                 log.info("Opening main application window");
194                 if (!handleCommandLine(args)) {
195                         BasicFrame.newAction();
196                 }
197                 
198                 // Check whether update info has been fetched or whether it needs more time
199                 log.info("Checking update status");
200                 checkUpdateStatus(updateInfo);
201         }
202         
203         
204
205         private static void loadMotor() {
206                 
207                 log.info("Starting motor loading from " + THRUSTCURVE_DIRECTORY + " in background thread.");
208                 ThrustCurveMotorSetDatabase db = new ThrustCurveMotorSetDatabase(true) {
209                         
210                         @Override
211                         protected void loadMotors() {
212                                 
213                                 // Block loading until timeout occurs or database is taken into use
214                                 log.info("Blocking motor loading while starting up");
215                                 while (!inUse && blockLoading.addAndGet(-100) > 0) {
216                                         try {
217                                                 Thread.sleep(100);
218                                         } catch (InterruptedException e) {
219                                         }
220                                 }
221                                 log.info("Blocking ended, inUse=" + inUse + " slowLoadingCount=" + blockLoading.get());
222                                 
223                                 // Start loading
224                                 log.info("Loading motors from " + THRUSTCURVE_DIRECTORY);
225                                 long t0 = System.currentTimeMillis();
226                                 int fileCount;
227                                 int thrustCurveCount;
228                                 
229                                 // Load the packaged thrust curves
230                                 List<Motor> list;
231                                 FileIterator iterator = DirectoryIterator.findDirectory(THRUSTCURVE_DIRECTORY,
232                                                                 new SimpleFileFilter("", false, "eng", "rse"));
233                                 if (iterator == null) {
234                                         throw new IllegalStateException("Thrust curve directory " + THRUSTCURVE_DIRECTORY +
235                                                         "not found, distribution built wrong");
236                                 }
237                                 list = MotorLoaderHelper.load(iterator);
238                                 for (Motor m : list) {
239                                         this.addMotor((ThrustCurveMotor) m);
240                                 }
241                                 fileCount = iterator.getFileCount();
242                                 
243                                 thrustCurveCount = list.size();
244                                 
245                                 // Load the user-defined thrust curves
246                                 for (File file : Prefs.getUserThrustCurveFiles()) {
247                                         // TODO: LOW: This counts a directory as one file
248                                         log.info("Loading motors from " + file);
249                                         list = MotorLoaderHelper.load(file);
250                                         for (Motor m : list) {
251                                                 this.addMotor((ThrustCurveMotor) m);
252                                         }
253                                         fileCount++;
254                                         thrustCurveCount += list.size();
255                                 }
256                                 
257                                 long t1 = System.currentTimeMillis();
258                                 
259                                 // Count statistics
260                                 int distinctMotorCount = 0;
261                                 int distinctThrustCurveCount = 0;
262                                 distinctMotorCount = motorSets.size();
263                                 for (ThrustCurveMotorSet set : motorSets) {
264                                         distinctThrustCurveCount += set.getMotorCount();
265                                 }
266                                 log.info("Motor loading done, took " + (t1 - t0) + " ms to load "
267                                                 + fileCount + " files/directories containing "
268                                                 + thrustCurveCount + " thrust curves which contained "
269                                                 + distinctMotorCount + " distinct motors with "
270                                                 + distinctThrustCurveCount + " distinct thrust curves.");
271                         }
272                         
273                 };
274                 db.startLoading();
275                 Application.setMotorSetDatabase(db);
276         }
277         
278         
279
280         private static void checkUpdateStatus(final UpdateInfoRetriever updateInfo) {
281                 if (updateInfo == null)
282                         return;
283                 
284                 int delay = 1000;
285                 if (!updateInfo.isRunning())
286                         delay = 100;
287                 
288                 final Timer timer = new Timer(delay, null);
289                 
290                 ActionListener listener = new ActionListener() {
291                         private int count = 5;
292                         
293                         @Override
294                         public void actionPerformed(ActionEvent e) {
295                                 if (!updateInfo.isRunning()) {
296                                         timer.stop();
297                                         
298                                         String current = Prefs.getVersion();
299                                         String last = Prefs.getString(Prefs.LAST_UPDATE, "");
300                                         
301                                         UpdateInfo info = updateInfo.getUpdateInfo();
302                                         if (info != null && info.getLatestVersion() != null &&
303                                                         !current.equals(info.getLatestVersion()) &&
304                                                         !last.equals(info.getLatestVersion())) {
305                                                 
306                                                 UpdateInfoDialog infoDialog = new UpdateInfoDialog(info);
307                                                 infoDialog.setVisible(true);
308                                                 if (infoDialog.isReminderSelected()) {
309                                                         Prefs.putString(Prefs.LAST_UPDATE, "");
310                                                 } else {
311                                                         Prefs.putString(Prefs.LAST_UPDATE, info.getLatestVersion());
312                                                 }
313                                         }
314                                 }
315                                 count--;
316                                 if (count <= 0)
317                                         timer.stop();
318                         }
319                 };
320                 timer.addActionListener(listener);
321                 timer.start();
322         }
323         
324         
325         /**
326          * Handles arguments passed from the command line.  This may be used either
327          * when starting the first instance of OpenRocket or later when OpenRocket is
328          * executed again while running.
329          * 
330          * @param args  the command-line arguments.
331          * @return              whether a new frame was opened or similar user desired action was
332          *                              performed as a result.
333          */
334         public static boolean handleCommandLine(String[] args) {
335                 
336                 // Check command-line for files
337                 boolean opened = false;
338                 for (String file : args) {
339                         if (BasicFrame.open(new File(file), null)) {
340                                 opened = true;
341                         }
342                 }
343                 return opened;
344         }
345         
346         
347
348         /**
349          * Check that the JRE is not running headless.
350          */
351         private static void checkHead() {
352                 
353                 log.info("Checking for graphics head");
354                 
355                 if (GraphicsEnvironment.isHeadless()) {
356                         log.error("Application is headless.");
357                         System.err.println();
358                         System.err.println("OpenRocket cannot currently be run without the graphical " +
359                                         "user interface.");
360                         System.err.println();
361                         System.exit(1);
362                 }
363                 
364         }
365         
366         
367         ///////////  Logging  ///////////
368         
369         private static void initializeLogging() {
370                 DelegatorLogger delegator = new DelegatorLogger();
371                 
372                 // Log buffer
373                 LogLevelBufferLogger buffer = new LogLevelBufferLogger(LOG_BUFFER_LENGTH);
374                 delegator.addLogger(buffer);
375                 
376                 // Check whether to log to stdout/stderr
377                 PrintStreamLogger printer = new PrintStreamLogger();
378                 boolean logout = setLogOutput(printer, System.out, System.getProperty(LOG_STDOUT_PROPERTY), null);
379                 boolean logerr = setLogOutput(printer, System.err, System.getProperty(LOG_STDERR_PROPERTY), LogLevel.ERROR);
380                 if (logout || logerr) {
381                         delegator.addLogger(printer);
382                 }
383                 
384                 // Set the loggers
385                 Application.setLogger(delegator);
386                 Application.setLogBuffer(buffer);
387                 
388                 // Initialize the log for this class
389                 log = Application.getLogger();
390                 log.info("Logging subsystem initialized for OpenRocket " + Prefs.getVersion());
391                 String str = "Console logging output:";
392                 for (LogLevel l : LogLevel.values()) {
393                         PrintStream ps = printer.getOutput(l);
394                         str += " " + l.name() + ":";
395                         if (ps == System.err) {
396                                 str += "stderr";
397                         } else if (ps == System.out) {
398                                 str += "stdout";
399                         } else {
400                                 str += "none";
401                         }
402                 }
403                 str += " (" + LOG_STDOUT_PROPERTY + "=" + System.getProperty(LOG_STDOUT_PROPERTY) +
404                                 " " + LOG_STDERR_PROPERTY + "=" + System.getProperty(LOG_STDERR_PROPERTY) + ")";
405                 log.info(str);
406         }
407         
408         private static boolean setLogOutput(PrintStreamLogger logger, PrintStream stream, String level, LogLevel defaultLevel) {
409                 LogLevel minLevel = LogLevel.fromString(level, defaultLevel);
410                 if (minLevel == null) {
411                         return false;
412                 }
413                 
414                 for (LogLevel l : LogLevel.values()) {
415                         if (l.atLeast(minLevel)) {
416                                 logger.setOutput(l, stream);
417                         }
418                 }
419                 return true;
420         }
421         
422         
423         ///////////  Helper methods  //////////
424         
425         /**
426          * Presents an error message to the user and exits the application.
427          * 
428          * @param message       an array of messages to present.
429          */
430         static void error(String[] message) {
431                 
432                 System.err.println();
433                 System.err.println("Error starting OpenRocket:");
434                 System.err.println();
435                 for (int i = 0; i < message.length; i++) {
436                         System.err.println(message[i]);
437                 }
438                 System.err.println();
439                 
440
441                 if (!GraphicsEnvironment.isHeadless()) {
442                         
443                         JOptionPane.showMessageDialog(null, message, "Error starting OpenRocket",
444                                         JOptionPane.ERROR_MESSAGE);
445                         
446                 }
447                 
448                 System.exit(1);
449         }
450         
451         
452         /**
453          * Presents the user with a message dialog and asks whether to continue.
454          * If the user does not select "Yes" the the application exits.
455          * 
456          * @param message       the message Strings to show.
457          */
458         static void confirm(String[] message) {
459                 
460                 if (!GraphicsEnvironment.isHeadless()) {
461                         
462                         if (JOptionPane.showConfirmDialog(null, message, "Error starting OpenRocket",
463                                         JOptionPane.YES_NO_OPTION) != JOptionPane.YES_OPTION) {
464                                 System.exit(1);
465                         }
466                 }
467         }
468         
469 }