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