1 package net.sf.openrocket.startup;
3 import java.awt.GraphicsEnvironment;
4 import java.awt.event.ActionEvent;
5 import java.awt.event.ActionListener;
7 import java.io.PrintStream;
9 import java.util.concurrent.atomic.AtomicInteger;
11 import javax.swing.JOptionPane;
12 import javax.swing.SwingUtilities;
13 import javax.swing.Timer;
14 import javax.swing.ToolTipManager;
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;
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.
45 * @author Sampo Niskanen <sampo.niskanen@iki.fi>
47 public class Startup {
49 private static LogHelper log;
51 private static final String LOG_STDERR_PROPERTY = "openrocket.log.stderr";
52 private static final String LOG_STDOUT_PROPERTY = "openrocket.log.stdout";
54 private static final int LOG_BUFFER_LENGTH = 50;
56 private static final String THRUSTCURVE_DIRECTORY = "datafiles/thrustcurves/";
59 /** Block motor loading for this many milliseconds */
60 private static AtomicInteger blockLoading = new AtomicInteger(Integer.MAX_VALUE);
63 public static void main(final String[] args) throws Exception {
65 // Check for "openrocket.debug" property before anything else
68 // Initialize logging first so we can use it
71 // Check that we have a head
74 // Check that we're running a good version of a JRE
75 log.info("Checking JRE compatibility");
76 VersionHelper.checkVersion();
77 VersionHelper.checkOpenJDK();
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() {
88 log.info("Startup complete");
90 // Block motor loading for 1.5 seconds to allow window painting
91 blockLoading.set(1500);
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.motordigest", "true");
109 private static void runMain(String[] args) {
111 // Initialize the splash screen with version info
112 log.info("Initializing the splash screen");
115 // Setup the uncaught exception handler
116 log.info("Registering exception handler");
117 ExceptionHandler.registerExceptionHandler();
119 // Start update info fetching
120 final UpdateInfoRetriever updateInfo;
121 if (Prefs.getCheckUpdates()) {
122 log.info("Starting update check");
123 updateInfo = new UpdateInfoRetriever();
126 log.info("Update check disabled");
130 // Set the best available look-and-feel
131 log.info("Setting best LAF");
132 GUIUtil.setBestLAF();
134 // Set tooltip delay time. Tooltips are used in MotorChooserDialog extensively.
135 ToolTipManager.sharedInstance().setDismissDelay(30000);
138 Prefs.loadDefaultUnits();
141 log.info("Loading databases");
143 Databases.fakeMethod();
145 // Starting action (load files or open new document)
146 log.info("Opening main application window");
147 if (!handleCommandLine(args)) {
148 BasicFrame.newAction();
151 // Check whether update info has been fetched or whether it needs more time
152 log.info("Checking update status");
153 checkUpdateStatus(updateInfo);
158 private static void loadMotor() {
160 log.info("Starting motor loading from " + THRUSTCURVE_DIRECTORY + " in background thread.");
161 ThrustCurveMotorSetDatabase db = new ThrustCurveMotorSetDatabase(true) {
164 protected void loadMotors() {
166 // Block loading until timeout occurs or database is taken into use
167 log.info("Blocking motor loading while starting up");
168 while (!inUse && blockLoading.addAndGet(-100) > 0) {
171 } catch (InterruptedException e) {
174 log.info("Blocking ended, inUse=" + inUse + " slowLoadingCount=" + blockLoading.get());
177 log.info("Loading motors from " + THRUSTCURVE_DIRECTORY);
178 long t0 = System.currentTimeMillis();
180 int thrustCurveCount;
182 // Load the packaged thrust curves
184 FileIterator iterator = DirectoryIterator.findDirectory(THRUSTCURVE_DIRECTORY,
185 new SimpleFileFilter("", false, "eng", "rse"));
186 if (iterator == null) {
187 throw new IllegalStateException("Thrust curve directory " + THRUSTCURVE_DIRECTORY +
188 "not found, distribution built wrong");
190 list = MotorLoaderHelper.load(iterator);
191 for (Motor m : list) {
192 this.addMotor((ThrustCurveMotor) m);
194 fileCount = iterator.getFileCount();
196 thrustCurveCount = list.size();
198 // Load the user-defined thrust curves
199 for (File file : Prefs.getUserThrustCurveFiles()) {
200 // TODO: LOW: This counts a directory as one file
201 log.info("Loading motors from " + file);
202 list = MotorLoaderHelper.load(file);
203 for (Motor m : list) {
204 this.addMotor((ThrustCurveMotor) m);
207 thrustCurveCount += list.size();
210 long t1 = System.currentTimeMillis();
213 int distinctMotorCount = 0;
214 int distinctThrustCurveCount = 0;
215 distinctMotorCount = motorSets.size();
216 for (ThrustCurveMotorSet set : motorSets) {
217 distinctThrustCurveCount += set.getMotorCount();
219 log.info("Motor loading done, took " + (t1 - t0) + " ms to load "
220 + fileCount + " files/directories containing "
221 + thrustCurveCount + " thrust curves which contained "
222 + distinctMotorCount + " distinct motors with "
223 + distinctThrustCurveCount + " distinct thrust curves.");
228 Application.setMotorSetDatabase(db);
233 private static void checkUpdateStatus(final UpdateInfoRetriever updateInfo) {
234 if (updateInfo == null)
238 if (!updateInfo.isRunning())
241 final Timer timer = new Timer(delay, null);
243 ActionListener listener = new ActionListener() {
244 private int count = 5;
247 public void actionPerformed(ActionEvent e) {
248 if (!updateInfo.isRunning()) {
251 String current = Prefs.getVersion();
252 String last = Prefs.getString(Prefs.LAST_UPDATE, "");
254 UpdateInfo info = updateInfo.getUpdateInfo();
255 if (info != null && info.getLatestVersion() != null &&
256 !current.equals(info.getLatestVersion()) &&
257 !last.equals(info.getLatestVersion())) {
259 UpdateInfoDialog infoDialog = new UpdateInfoDialog(info);
260 infoDialog.setVisible(true);
261 if (infoDialog.isReminderSelected()) {
262 Prefs.putString(Prefs.LAST_UPDATE, "");
264 Prefs.putString(Prefs.LAST_UPDATE, info.getLatestVersion());
273 timer.addActionListener(listener);
279 * Handles arguments passed from the command line. This may be used either
280 * when starting the first instance of OpenRocket or later when OpenRocket is
281 * executed again while running.
283 * @param args the command-line arguments.
284 * @return whether a new frame was opened or similar user desired action was
285 * performed as a result.
287 public static boolean handleCommandLine(String[] args) {
289 // Check command-line for files
290 boolean opened = false;
291 for (String file : args) {
292 if (BasicFrame.open(new File(file), null)) {
302 * Check that the JRE is not running headless.
304 private static void checkHead() {
306 log.info("Checking for graphics head");
308 if (GraphicsEnvironment.isHeadless()) {
309 log.error("Application is headless.");
310 System.err.println();
311 System.err.println("OpenRocket cannot currently be run without the graphical " +
313 System.err.println();
320 /////////// Logging ///////////
322 private static void initializeLogging() {
323 DelegatorLogger delegator = new DelegatorLogger();
326 LogLevelBufferLogger buffer = new LogLevelBufferLogger(LOG_BUFFER_LENGTH);
327 delegator.addLogger(buffer);
329 // Check whether to log to stdout/stderr
330 PrintStreamLogger printer = new PrintStreamLogger();
331 boolean logout = setLogOutput(printer, System.out, System.getProperty(LOG_STDOUT_PROPERTY), null);
332 boolean logerr = setLogOutput(printer, System.err, System.getProperty(LOG_STDERR_PROPERTY), LogLevel.WARN);
333 if (logout || logerr) {
334 delegator.addLogger(printer);
338 Application.setLogger(delegator);
339 Application.setLogBuffer(buffer);
341 // Initialize the log for this class
342 log = Application.getLogger();
343 log.info("Logging subsystem initialized for OpenRocket " + Prefs.getVersion());
344 String str = "Console logging output:";
345 for (LogLevel l : LogLevel.values()) {
346 PrintStream ps = printer.getOutput(l);
347 str += " " + l.name() + ":";
348 if (ps == System.err) {
350 } else if (ps == System.out) {
356 str += " (" + LOG_STDOUT_PROPERTY + "=" + System.getProperty(LOG_STDOUT_PROPERTY) +
357 " " + LOG_STDERR_PROPERTY + "=" + System.getProperty(LOG_STDERR_PROPERTY) + ")";
361 private static boolean setLogOutput(PrintStreamLogger logger, PrintStream stream, String level, LogLevel defaultLevel) {
362 LogLevel minLevel = LogLevel.fromString(level, defaultLevel);
363 if (minLevel == null) {
367 for (LogLevel l : LogLevel.values()) {
368 if (l.atLeast(minLevel)) {
369 logger.setOutput(l, stream);
376 /////////// Helper methods //////////
379 * Presents an error message to the user and exits the application.
381 * @param message an array of messages to present.
383 static void error(String[] message) {
385 System.err.println();
386 System.err.println("Error starting OpenRocket:");
387 System.err.println();
388 for (int i = 0; i < message.length; i++) {
389 System.err.println(message[i]);
391 System.err.println();
394 if (!GraphicsEnvironment.isHeadless()) {
396 JOptionPane.showMessageDialog(null, message, "Error starting OpenRocket",
397 JOptionPane.ERROR_MESSAGE);
406 * Presents the user with a message dialog and asks whether to continue.
407 * If the user does not select "Yes" the the application exits.
409 * @param message the message Strings to show.
411 static void confirm(String[] message) {
413 if (!GraphicsEnvironment.isHeadless()) {
415 if (JOptionPane.showConfirmDialog(null, message, "Error starting OpenRocket",
416 JOptionPane.YES_NO_OPTION) != JOptionPane.YES_OPTION) {