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.IOException;
8 import java.io.InputStream;
9 import java.io.PrintStream;
10 import java.util.List;
11 import java.util.concurrent.atomic.AtomicInteger;
13 import javax.swing.JOptionPane;
14 import javax.swing.SwingUtilities;
15 import javax.swing.Timer;
16 import javax.swing.ToolTipManager;
18 import net.sf.openrocket.communication.UpdateInfo;
19 import net.sf.openrocket.communication.UpdateInfoRetriever;
20 import net.sf.openrocket.database.Databases;
21 import net.sf.openrocket.database.ThrustCurveMotorSet;
22 import net.sf.openrocket.database.ThrustCurveMotorSetDatabase;
23 import net.sf.openrocket.file.DirectoryIterator;
24 import net.sf.openrocket.file.GeneralMotorLoader;
25 import net.sf.openrocket.gui.dialogs.UpdateInfoDialog;
26 import net.sf.openrocket.gui.main.BasicFrame;
27 import net.sf.openrocket.gui.main.ExceptionHandler;
28 import net.sf.openrocket.gui.main.SimpleFileFilter;
29 import net.sf.openrocket.gui.main.Splash;
30 import net.sf.openrocket.logging.DelegatorLogger;
31 import net.sf.openrocket.logging.LogHelper;
32 import net.sf.openrocket.logging.LogLevel;
33 import net.sf.openrocket.logging.LogLevelBufferLogger;
34 import net.sf.openrocket.logging.PrintStreamLogger;
35 import net.sf.openrocket.motor.Motor;
36 import net.sf.openrocket.motor.ThrustCurveMotor;
37 import net.sf.openrocket.util.GUIUtil;
38 import net.sf.openrocket.util.Pair;
39 import net.sf.openrocket.util.Prefs;
43 * A startup class that checks that a suitable JRE environment is being run.
44 * If the environment is too old the execution is canceled, and if OpenJDK is being
45 * used warns the user of problems and confirms whether to continue.
47 * @author Sampo Niskanen <sampo.niskanen@iki.fi>
49 public class Startup {
51 private static LogHelper log;
53 private static final String LOG_STDERR_PROPERTY = "openrocket.log.stderr";
54 private static final String LOG_STDOUT_PROPERTY = "openrocket.log.stdout";
56 private static final int LOG_BUFFER_LENGTH = 50;
58 private static final String THRUSTCURVE_DIRECTORY = "datafiles/thrustcurves/";
61 /** Block motor loading for this many milliseconds */
62 private static AtomicInteger blockLoading = new AtomicInteger(Integer.MAX_VALUE);
65 public static void main(final String[] args) throws Exception {
67 // Initialize logging first so we can use it
70 // Check that we have a head
73 // Check that we're running a good version of a JRE
74 log.info("Checking JRE compatibility");
75 VersionHelper.checkVersion();
76 VersionHelper.checkOpenJDK();
78 // Run the actual startup method in the EDT since it can use progress dialogs etc.
79 log.info("Running main");
80 SwingUtilities.invokeAndWait(new Runnable() {
87 log.info("Startup complete");
89 // Block motor loading for 2 seconds to allow window painting
90 blockLoading.set(2000);
96 private static void runMain(String[] args) {
98 // Initialize the splash screen with version info
99 log.info("Initializing the splash screen");
102 // Setup the uncaught exception handler
103 log.info("Registering exception handler");
104 ExceptionHandler.registerExceptionHandler();
106 // Start update info fetching
107 final UpdateInfoRetriever updateInfo;
108 if (Prefs.getCheckUpdates()) {
109 log.info("Starting update check");
110 updateInfo = new UpdateInfoRetriever();
113 log.info("Update check disabled");
117 // Set the best available look-and-feel
118 log.info("Setting best LAF");
119 GUIUtil.setBestLAF();
121 // Set tooltip delay time. Tooltips are used in MotorChooserDialog extensively.
122 ToolTipManager.sharedInstance().setDismissDelay(30000);
125 Prefs.loadDefaultUnits();
128 // TODO: HIGH: Use new motor loading
129 log.info("Loading databases");
131 Databases.fakeMethod();
133 // Starting action (load files or open new document)
134 log.info("Opening main application window");
135 if (!handleCommandLine(args)) {
136 BasicFrame.newAction();
139 // Check whether update info has been fetched or whether it needs more time
140 log.info("Checking update status");
141 checkUpdateStatus(updateInfo);
146 private static void loadMotor() {
148 log.info("Starting motor loading from " + THRUSTCURVE_DIRECTORY +
149 " in background thread.");
150 ThrustCurveMotorSetDatabase db = new ThrustCurveMotorSetDatabase(true) {
153 protected void loadMotors() {
155 log.info("Blocking motor loading while starting up");
157 // Block for 100ms a time until timeout or database in use
158 while (!inUse && blockLoading.addAndGet(-100) > 0) {
161 } catch (InterruptedException e) {
165 log.info("Blocking ended, inUse=" + inUse + " slowLoadingCount=" + blockLoading.get());
167 log.info("Started to load motors from " + THRUSTCURVE_DIRECTORY);
168 long t0 = System.currentTimeMillis();
171 int thrustCurveCount = 0;
172 int distinctMotorCount = 0;
173 int distinctThrustCurveCount = 0;
175 GeneralMotorLoader loader = new GeneralMotorLoader();
176 DirectoryIterator iterator = DirectoryIterator.findDirectory(THRUSTCURVE_DIRECTORY,
177 new SimpleFileFilter("", false, "eng", "rse"));
178 if (iterator == null) {
179 throw new IllegalStateException("No thrust curves found, distribution built wrong");
181 while (iterator.hasNext()) {
182 final Pair<String, InputStream> input = iterator.next();
183 log.debug("Loading motors from file " + input.getU());
186 List<Motor> motors = loader.load(input.getV(), input.getU());
187 if (motors.size() == 0) {
188 log.warn("No motors found in file " + input.getU());
190 for (Motor m : motors) {
192 this.addMotor((ThrustCurveMotor) m);
194 } catch (IOException e) {
195 log.warn("IOException when loading motor file " + input.getU(), e);
198 input.getV().close();
199 } catch (IOException e) {
200 log.error("IOException when closing InputStream", e);
206 long t1 = System.currentTimeMillis();
209 distinctMotorCount = motorSets.size();
210 for (ThrustCurveMotorSet set : motorSets) {
211 distinctThrustCurveCount += set.getMotorCount();
213 log.info("Motor loading done, took " + (t1 - t0) + " ms to load "
214 + fileCount + " files containing " + thrustCurveCount + " thrust curves which contained "
215 + distinctMotorCount + " distinct motors with " + distinctThrustCurveCount + " thrust curves.");
220 Application.setMotorSetDatabase(db);
225 private static void checkUpdateStatus(final UpdateInfoRetriever updateInfo) {
226 if (updateInfo == null)
230 if (!updateInfo.isRunning())
233 final Timer timer = new Timer(delay, null);
235 ActionListener listener = new ActionListener() {
236 private int count = 5;
239 public void actionPerformed(ActionEvent e) {
240 if (!updateInfo.isRunning()) {
243 String current = Prefs.getVersion();
244 String last = Prefs.getString(Prefs.LAST_UPDATE, "");
246 UpdateInfo info = updateInfo.getUpdateInfo();
247 if (info != null && info.getLatestVersion() != null &&
248 !current.equals(info.getLatestVersion()) &&
249 !last.equals(info.getLatestVersion())) {
251 UpdateInfoDialog infoDialog = new UpdateInfoDialog(info);
252 infoDialog.setVisible(true);
253 if (infoDialog.isReminderSelected()) {
254 Prefs.putString(Prefs.LAST_UPDATE, "");
256 Prefs.putString(Prefs.LAST_UPDATE, info.getLatestVersion());
265 timer.addActionListener(listener);
271 * Handles arguments passed from the command line. This may be used either
272 * when starting the first instance of OpenRocket or later when OpenRocket is
273 * executed again while running.
275 * @param args the command-line arguments.
276 * @return whether a new frame was opened or similar user desired action was
277 * performed as a result.
279 public static boolean handleCommandLine(String[] args) {
281 // Check command-line for files
282 boolean opened = false;
283 for (String file : args) {
284 if (BasicFrame.open(new File(file), null)) {
294 * Check that the JRE is not running headless.
296 private static void checkHead() {
298 log.info("Checking for graphics head");
300 if (GraphicsEnvironment.isHeadless()) {
301 log.error("Application is headless.");
302 System.err.println();
303 System.err.println("OpenRocket cannot currently be run without the graphical " +
305 System.err.println();
312 /////////// Logging ///////////
314 private static void initializeLogging() {
315 DelegatorLogger delegator = new DelegatorLogger();
318 LogLevelBufferLogger buffer = new LogLevelBufferLogger(LOG_BUFFER_LENGTH);
319 delegator.addLogger(buffer);
321 // Check whether to log to stdout/stderr
322 PrintStreamLogger printer = new PrintStreamLogger();
323 boolean logout = setLogOutput(printer, System.out, System.getProperty(LOG_STDOUT_PROPERTY), null);
324 boolean logerr = setLogOutput(printer, System.err, System.getProperty(LOG_STDERR_PROPERTY), LogLevel.WARN);
325 if (logout || logerr) {
326 delegator.addLogger(printer);
330 Application.setLogger(delegator);
331 Application.setLogBuffer(buffer);
333 // Initialize the log for this class
334 log = Application.getLogger();
335 log.info("Logging subsystem initialized for OpenRocket " + Prefs.getVersion());
336 String str = "Console logging output:";
337 for (LogLevel l : LogLevel.values()) {
338 PrintStream ps = printer.getOutput(l);
339 str += " " + l.name() + ":";
340 if (ps == System.err) {
342 } else if (ps == System.out) {
348 str += " (" + LOG_STDOUT_PROPERTY + "=" + System.getProperty(LOG_STDOUT_PROPERTY) +
349 " " + LOG_STDERR_PROPERTY + "=" + System.getProperty(LOG_STDERR_PROPERTY) + ")";
353 private static boolean setLogOutput(PrintStreamLogger logger, PrintStream stream, String level, LogLevel defaultLevel) {
354 LogLevel minLevel = LogLevel.fromString(level, defaultLevel);
355 if (minLevel == null) {
359 for (LogLevel l : LogLevel.values()) {
360 if (l.atLeast(minLevel)) {
361 logger.setOutput(l, stream);
368 /////////// Helper methods //////////
371 * Presents an error message to the user and exits the application.
373 * @param message an array of messages to present.
375 static void error(String[] message) {
377 System.err.println();
378 System.err.println("Error starting OpenRocket:");
379 System.err.println();
380 for (int i = 0; i < message.length; i++) {
381 System.err.println(message[i]);
383 System.err.println();
386 if (!GraphicsEnvironment.isHeadless()) {
388 JOptionPane.showMessageDialog(null, message, "Error starting OpenRocket",
389 JOptionPane.ERROR_MESSAGE);
398 * Presents the user with a message dialog and asks whether to continue.
399 * If the user does not select "Yes" the the application exits.
401 * @param message the message Strings to show.
403 static void confirm(String[] message) {
405 if (!GraphicsEnvironment.isHeadless()) {
407 if (JOptionPane.showConfirmDialog(null, message, "Error starting OpenRocket",
408 JOptionPane.YES_NO_OPTION) != JOptionPane.YES_OPTION) {