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 // Initialize logging first so we can use it
68 // Check that we have a head
71 // Check that we're running a good version of a JRE
72 log.info("Checking JRE compatibility");
73 VersionHelper.checkVersion();
74 VersionHelper.checkOpenJDK();
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() {
85 log.info("Startup complete");
87 // Block motor loading for 1.5 seconds to allow window painting
88 blockLoading.set(1500);
94 private static void runMain(String[] args) {
96 // Initialize the splash screen with version info
97 log.info("Initializing the splash screen");
100 // Setup the uncaught exception handler
101 log.info("Registering exception handler");
102 ExceptionHandler.registerExceptionHandler();
104 // Start update info fetching
105 final UpdateInfoRetriever updateInfo;
106 if (Prefs.getCheckUpdates()) {
107 log.info("Starting update check");
108 updateInfo = new UpdateInfoRetriever();
111 log.info("Update check disabled");
115 // Set the best available look-and-feel
116 log.info("Setting best LAF");
117 GUIUtil.setBestLAF();
119 // Set tooltip delay time. Tooltips are used in MotorChooserDialog extensively.
120 ToolTipManager.sharedInstance().setDismissDelay(30000);
123 Prefs.loadDefaultUnits();
126 log.info("Loading databases");
128 Databases.fakeMethod();
130 // Starting action (load files or open new document)
131 log.info("Opening main application window");
132 if (!handleCommandLine(args)) {
133 BasicFrame.newAction();
136 // Check whether update info has been fetched or whether it needs more time
137 log.info("Checking update status");
138 checkUpdateStatus(updateInfo);
143 private static void loadMotor() {
145 log.info("Starting motor loading from " + THRUSTCURVE_DIRECTORY + " in background thread.");
146 ThrustCurveMotorSetDatabase db = new ThrustCurveMotorSetDatabase(true) {
149 protected void loadMotors() {
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) {
156 } catch (InterruptedException e) {
159 log.info("Blocking ended, inUse=" + inUse + " slowLoadingCount=" + blockLoading.get());
162 log.info("Loading motors from " + THRUSTCURVE_DIRECTORY);
163 long t0 = System.currentTimeMillis();
165 int thrustCurveCount;
167 // Load the packaged thrust curves
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");
175 list = MotorLoaderHelper.load(iterator);
176 for (Motor m : list) {
177 this.addMotor((ThrustCurveMotor) m);
179 fileCount = iterator.getFileCount();
181 thrustCurveCount = list.size();
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);
192 thrustCurveCount += list.size();
195 long t1 = System.currentTimeMillis();
198 int distinctMotorCount = 0;
199 int distinctThrustCurveCount = 0;
200 distinctMotorCount = motorSets.size();
201 for (ThrustCurveMotorSet set : motorSets) {
202 distinctThrustCurveCount += set.getMotorCount();
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.");
213 Application.setMotorSetDatabase(db);
218 private static void checkUpdateStatus(final UpdateInfoRetriever updateInfo) {
219 if (updateInfo == null)
223 if (!updateInfo.isRunning())
226 final Timer timer = new Timer(delay, null);
228 ActionListener listener = new ActionListener() {
229 private int count = 5;
232 public void actionPerformed(ActionEvent e) {
233 if (!updateInfo.isRunning()) {
236 String current = Prefs.getVersion();
237 String last = Prefs.getString(Prefs.LAST_UPDATE, "");
239 UpdateInfo info = updateInfo.getUpdateInfo();
240 if (info != null && info.getLatestVersion() != null &&
241 !current.equals(info.getLatestVersion()) &&
242 !last.equals(info.getLatestVersion())) {
244 UpdateInfoDialog infoDialog = new UpdateInfoDialog(info);
245 infoDialog.setVisible(true);
246 if (infoDialog.isReminderSelected()) {
247 Prefs.putString(Prefs.LAST_UPDATE, "");
249 Prefs.putString(Prefs.LAST_UPDATE, info.getLatestVersion());
258 timer.addActionListener(listener);
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.
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.
272 public static boolean handleCommandLine(String[] args) {
274 // Check command-line for files
275 boolean opened = false;
276 for (String file : args) {
277 if (BasicFrame.open(new File(file), null)) {
287 * Check that the JRE is not running headless.
289 private static void checkHead() {
291 log.info("Checking for graphics head");
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 " +
298 System.err.println();
305 /////////// Logging ///////////
307 private static void initializeLogging() {
308 DelegatorLogger delegator = new DelegatorLogger();
311 LogLevelBufferLogger buffer = new LogLevelBufferLogger(LOG_BUFFER_LENGTH);
312 delegator.addLogger(buffer);
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);
323 Application.setLogger(delegator);
324 Application.setLogBuffer(buffer);
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) {
335 } else if (ps == System.out) {
341 str += " (" + LOG_STDOUT_PROPERTY + "=" + System.getProperty(LOG_STDOUT_PROPERTY) +
342 " " + LOG_STDERR_PROPERTY + "=" + System.getProperty(LOG_STDERR_PROPERTY) + ")";
346 private static boolean setLogOutput(PrintStreamLogger logger, PrintStream stream, String level, LogLevel defaultLevel) {
347 LogLevel minLevel = LogLevel.fromString(level, defaultLevel);
348 if (minLevel == null) {
352 for (LogLevel l : LogLevel.values()) {
353 if (l.atLeast(minLevel)) {
354 logger.setOutput(l, stream);
361 /////////// Helper methods //////////
364 * Presents an error message to the user and exits the application.
366 * @param message an array of messages to present.
368 static void error(String[] message) {
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]);
376 System.err.println();
379 if (!GraphicsEnvironment.isHeadless()) {
381 JOptionPane.showMessageDialog(null, message, "Error starting OpenRocket",
382 JOptionPane.ERROR_MESSAGE);
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.
394 * @param message the message Strings to show.
396 static void confirm(String[] message) {
398 if (!GraphicsEnvironment.isHeadless()) {
400 if (JOptionPane.showConfirmDialog(null, message, "Error starting OpenRocket",
401 JOptionPane.YES_NO_OPTION) != JOptionPane.YES_OPTION) {