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