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.mutexlocation", "true");
103 System.setProperty("openrocket.debug.motordigest", "true");
110 private static void runMain(String[] args) {
112 // Initialize the splash screen with version info
113 log.info("Initializing the splash screen");
116 // Setup the uncaught exception handler
117 log.info("Registering exception handler");
118 ExceptionHandler.registerExceptionHandler();
120 // Start update info fetching
121 final UpdateInfoRetriever updateInfo;
122 if (Prefs.getCheckUpdates()) {
123 log.info("Starting update check");
124 updateInfo = new UpdateInfoRetriever();
127 log.info("Update check disabled");
131 // Set the best available look-and-feel
132 log.info("Setting best LAF");
133 GUIUtil.setBestLAF();
135 // Set tooltip delay time. Tooltips are used in MotorChooserDialog extensively.
136 ToolTipManager.sharedInstance().setDismissDelay(30000);
139 Prefs.loadDefaultUnits();
142 log.info("Loading databases");
144 Databases.fakeMethod();
146 // Starting action (load files or open new document)
147 log.info("Opening main application window");
148 if (!handleCommandLine(args)) {
149 BasicFrame.newAction();
152 // Check whether update info has been fetched or whether it needs more time
153 log.info("Checking update status");
154 checkUpdateStatus(updateInfo);
159 private static void loadMotor() {
161 log.info("Starting motor loading from " + THRUSTCURVE_DIRECTORY + " in background thread.");
162 ThrustCurveMotorSetDatabase db = new ThrustCurveMotorSetDatabase(true) {
165 protected void loadMotors() {
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) {
172 } catch (InterruptedException e) {
175 log.info("Blocking ended, inUse=" + inUse + " slowLoadingCount=" + blockLoading.get());
178 log.info("Loading motors from " + THRUSTCURVE_DIRECTORY);
179 long t0 = System.currentTimeMillis();
181 int thrustCurveCount;
183 // Load the packaged thrust curves
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");
191 list = MotorLoaderHelper.load(iterator);
192 for (Motor m : list) {
193 this.addMotor((ThrustCurveMotor) m);
195 fileCount = iterator.getFileCount();
197 thrustCurveCount = list.size();
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);
208 thrustCurveCount += list.size();
211 long t1 = System.currentTimeMillis();
214 int distinctMotorCount = 0;
215 int distinctThrustCurveCount = 0;
216 distinctMotorCount = motorSets.size();
217 for (ThrustCurveMotorSet set : motorSets) {
218 distinctThrustCurveCount += set.getMotorCount();
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.");
229 Application.setMotorSetDatabase(db);
234 private static void checkUpdateStatus(final UpdateInfoRetriever updateInfo) {
235 if (updateInfo == null)
239 if (!updateInfo.isRunning())
242 final Timer timer = new Timer(delay, null);
244 ActionListener listener = new ActionListener() {
245 private int count = 5;
248 public void actionPerformed(ActionEvent e) {
249 if (!updateInfo.isRunning()) {
252 String current = Prefs.getVersion();
253 String last = Prefs.getString(Prefs.LAST_UPDATE, "");
255 UpdateInfo info = updateInfo.getUpdateInfo();
256 if (info != null && info.getLatestVersion() != null &&
257 !current.equals(info.getLatestVersion()) &&
258 !last.equals(info.getLatestVersion())) {
260 UpdateInfoDialog infoDialog = new UpdateInfoDialog(info);
261 infoDialog.setVisible(true);
262 if (infoDialog.isReminderSelected()) {
263 Prefs.putString(Prefs.LAST_UPDATE, "");
265 Prefs.putString(Prefs.LAST_UPDATE, info.getLatestVersion());
274 timer.addActionListener(listener);
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.
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.
288 public static boolean handleCommandLine(String[] args) {
290 // Check command-line for files
291 boolean opened = false;
292 for (String file : args) {
293 if (BasicFrame.open(new File(file), null)) {
303 * Check that the JRE is not running headless.
305 private static void checkHead() {
307 log.info("Checking for graphics head");
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 " +
314 System.err.println();
321 /////////// Logging ///////////
323 private static void initializeLogging() {
324 DelegatorLogger delegator = new DelegatorLogger();
327 LogLevelBufferLogger buffer = new LogLevelBufferLogger(LOG_BUFFER_LENGTH);
328 delegator.addLogger(buffer);
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);
339 Application.setLogger(delegator);
340 Application.setLogBuffer(buffer);
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) {
351 } else if (ps == System.out) {
357 str += " (" + LOG_STDOUT_PROPERTY + "=" + System.getProperty(LOG_STDOUT_PROPERTY) +
358 " " + LOG_STDERR_PROPERTY + "=" + System.getProperty(LOG_STDERR_PROPERTY) + ")";
362 private static boolean setLogOutput(PrintStreamLogger logger, PrintStream stream, String level, LogLevel defaultLevel) {
363 LogLevel minLevel = LogLevel.fromString(level, defaultLevel);
364 if (minLevel == null) {
368 for (LogLevel l : LogLevel.values()) {
369 if (l.atLeast(minLevel)) {
370 logger.setOutput(l, stream);
377 /////////// Helper methods //////////
380 * Presents an error message to the user and exits the application.
382 * @param message an array of messages to present.
384 static void error(String[] message) {
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]);
392 System.err.println();
395 if (!GraphicsEnvironment.isHeadless()) {
397 JOptionPane.showMessageDialog(null, message, "Error starting OpenRocket",
398 JOptionPane.ERROR_MESSAGE);
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.
410 * @param message the message Strings to show.
412 static void confirm(String[] message) {
414 if (!GraphicsEnvironment.isHeadless()) {
416 if (JOptionPane.showConfirmDialog(null, message, "Error starting OpenRocket",
417 JOptionPane.YES_NO_OPTION) != JOptionPane.YES_OPTION) {