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.Locale;
10 import java.util.concurrent.atomic.AtomicInteger;
12 import javax.swing.JOptionPane;
13 import javax.swing.SwingUtilities;
14 import javax.swing.Timer;
15 import javax.swing.ToolTipManager;
17 import net.sf.openrocket.communication.UpdateInfo;
18 import net.sf.openrocket.communication.UpdateInfoRetriever;
19 import net.sf.openrocket.database.Databases;
20 import net.sf.openrocket.database.ThrustCurveMotorSet;
21 import net.sf.openrocket.database.ThrustCurveMotorSetDatabase;
22 import net.sf.openrocket.file.iterator.DirectoryIterator;
23 import net.sf.openrocket.file.iterator.FileIterator;
24 import net.sf.openrocket.file.motor.MotorLoaderHelper;
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.l10n.DebugTranslator;
31 import net.sf.openrocket.l10n.ResourceBundleTranslator;
32 import net.sf.openrocket.l10n.Translator;
33 import net.sf.openrocket.logging.DelegatorLogger;
34 import net.sf.openrocket.logging.LogHelper;
35 import net.sf.openrocket.logging.LogLevel;
36 import net.sf.openrocket.logging.LogLevelBufferLogger;
37 import net.sf.openrocket.logging.PrintStreamLogger;
38 import net.sf.openrocket.motor.Motor;
39 import net.sf.openrocket.motor.ThrustCurveMotor;
40 import net.sf.openrocket.util.GUIUtil;
41 import net.sf.openrocket.util.Prefs;
45 * A startup class that checks that a suitable JRE environment is being run.
46 * If the environment is too old the execution is canceled, and if OpenJDK is being
47 * used warns the user of problems and confirms whether to continue.
49 * @author Sampo Niskanen <sampo.niskanen@iki.fi>
51 public class Startup {
53 private static LogHelper log;
55 private static final String LOG_STDERR_PROPERTY = "openrocket.log.stderr";
56 private static final String LOG_STDOUT_PROPERTY = "openrocket.log.stdout";
58 private static final int LOG_BUFFER_LENGTH = 50;
60 private static final String THRUSTCURVE_DIRECTORY = "datafiles/thrustcurves/";
63 /** Block motor loading for this many milliseconds */
64 private static AtomicInteger blockLoading = new AtomicInteger(Integer.MAX_VALUE);
67 public static void main(final String[] args) throws Exception {
69 // Check for "openrocket.debug" property before anything else
72 // Initialize logging first so we can use it
75 // Setup the translations
78 // Check that we have a head
81 // Check that we're running a good version of a JRE
82 log.info("Checking JRE compatibility");
83 VersionHelper.checkVersion();
84 VersionHelper.checkOpenJDK();
86 // Run the actual startup method in the EDT since it can use progress dialogs etc.
87 log.info("Running main");
88 SwingUtilities.invokeAndWait(new Runnable() {
95 log.info("Startup complete");
97 // Block motor loading for 1.5 seconds to allow window painting
98 blockLoading.set(1500);
105 private static void checkDebugStatus() {
106 if (System.getProperty("openrocket.debug") != null) {
107 setPropertyIfNotSet("openrocket.log.stdout", "VBOSE");
108 setPropertyIfNotSet("openrocket.log.tracelevel", "VBOSE");
109 setPropertyIfNotSet("openrocket.debug.menu", "true");
110 setPropertyIfNotSet("openrocket.debug.mutexlocation", "true");
111 setPropertyIfNotSet("openrocket.debug.motordigest", "true");
115 private static void setPropertyIfNotSet(String key, String value) {
116 if (System.getProperty(key) == null) {
117 System.setProperty(key, value);
123 * Initializes the localization system.
125 private static void initializeL10n() {
126 String locale = System.getProperty("openrocket.locale");
127 if (locale != null) {
129 String[] split = locale.split("[_-]", 3);
130 if (split.length == 1) {
131 l = new Locale(split[0]);
132 } else if (split.length == 2) {
133 l = new Locale(split[0], split[1]);
135 l = new Locale(split[0], split[1], split[2]);
137 log.info("Setting custom locale " + l);
138 Locale.setDefault(l);
142 t = new ResourceBundleTranslator("l10n.messages");
143 if (Locale.getDefault().getLanguage().equals("xx")) {
144 t = new DebugTranslator(t);
147 log.info("Set up translation for locale " + Locale.getDefault() +
148 ", debug.currentFile=" + t.get("debug.currentFile"));
150 Application.setBaseTranslator(t);
155 private static void runMain(String[] args) {
157 // Initialize the splash screen with version info
158 log.info("Initializing the splash screen");
161 // Setup the uncaught exception handler
162 log.info("Registering exception handler");
163 ExceptionHandler.registerExceptionHandler();
165 // Start update info fetching
166 final UpdateInfoRetriever updateInfo;
167 if (Prefs.getCheckUpdates()) {
168 log.info("Starting update check");
169 updateInfo = new UpdateInfoRetriever();
172 log.info("Update check disabled");
176 // Set the best available look-and-feel
177 log.info("Setting best LAF");
178 GUIUtil.setBestLAF();
180 // Set tooltip delay time. Tooltips are used in MotorChooserDialog extensively.
181 ToolTipManager.sharedInstance().setDismissDelay(30000);
184 Prefs.loadDefaultUnits();
187 log.info("Loading databases");
189 Databases.fakeMethod();
191 // Starting action (load files or open new document)
192 log.info("Opening main application window");
193 if (!handleCommandLine(args)) {
194 BasicFrame.newAction();
197 // Check whether update info has been fetched or whether it needs more time
198 log.info("Checking update status");
199 checkUpdateStatus(updateInfo);
204 private static void loadMotor() {
206 log.info("Starting motor loading from " + THRUSTCURVE_DIRECTORY + " in background thread.");
207 ThrustCurveMotorSetDatabase db = new ThrustCurveMotorSetDatabase(true) {
210 protected void loadMotors() {
212 // Block loading until timeout occurs or database is taken into use
213 log.info("Blocking motor loading while starting up");
214 while (!inUse && blockLoading.addAndGet(-100) > 0) {
217 } catch (InterruptedException e) {
220 log.info("Blocking ended, inUse=" + inUse + " slowLoadingCount=" + blockLoading.get());
223 log.info("Loading motors from " + THRUSTCURVE_DIRECTORY);
224 long t0 = System.currentTimeMillis();
226 int thrustCurveCount;
228 // Load the packaged thrust curves
230 FileIterator iterator = DirectoryIterator.findDirectory(THRUSTCURVE_DIRECTORY,
231 new SimpleFileFilter("", false, "eng", "rse"));
232 if (iterator == null) {
233 throw new IllegalStateException("Thrust curve directory " + THRUSTCURVE_DIRECTORY +
234 "not found, distribution built wrong");
236 list = MotorLoaderHelper.load(iterator);
237 for (Motor m : list) {
238 this.addMotor((ThrustCurveMotor) m);
240 fileCount = iterator.getFileCount();
242 thrustCurveCount = list.size();
244 // Load the user-defined thrust curves
245 for (File file : Prefs.getUserThrustCurveFiles()) {
246 // TODO: LOW: This counts a directory as one file
247 log.info("Loading motors from " + file);
248 list = MotorLoaderHelper.load(file);
249 for (Motor m : list) {
250 this.addMotor((ThrustCurveMotor) m);
253 thrustCurveCount += list.size();
256 long t1 = System.currentTimeMillis();
259 int distinctMotorCount = 0;
260 int distinctThrustCurveCount = 0;
261 distinctMotorCount = motorSets.size();
262 for (ThrustCurveMotorSet set : motorSets) {
263 distinctThrustCurveCount += set.getMotorCount();
265 log.info("Motor loading done, took " + (t1 - t0) + " ms to load "
266 + fileCount + " files/directories containing "
267 + thrustCurveCount + " thrust curves which contained "
268 + distinctMotorCount + " distinct motors with "
269 + distinctThrustCurveCount + " distinct thrust curves.");
274 Application.setMotorSetDatabase(db);
279 private static void checkUpdateStatus(final UpdateInfoRetriever updateInfo) {
280 if (updateInfo == null)
284 if (!updateInfo.isRunning())
287 final Timer timer = new Timer(delay, null);
289 ActionListener listener = new ActionListener() {
290 private int count = 5;
293 public void actionPerformed(ActionEvent e) {
294 if (!updateInfo.isRunning()) {
297 String current = Prefs.getVersion();
298 String last = Prefs.getString(Prefs.LAST_UPDATE, "");
300 UpdateInfo info = updateInfo.getUpdateInfo();
301 if (info != null && info.getLatestVersion() != null &&
302 !current.equals(info.getLatestVersion()) &&
303 !last.equals(info.getLatestVersion())) {
305 UpdateInfoDialog infoDialog = new UpdateInfoDialog(info);
306 infoDialog.setVisible(true);
307 if (infoDialog.isReminderSelected()) {
308 Prefs.putString(Prefs.LAST_UPDATE, "");
310 Prefs.putString(Prefs.LAST_UPDATE, info.getLatestVersion());
319 timer.addActionListener(listener);
325 * Handles arguments passed from the command line. This may be used either
326 * when starting the first instance of OpenRocket or later when OpenRocket is
327 * executed again while running.
329 * @param args the command-line arguments.
330 * @return whether a new frame was opened or similar user desired action was
331 * performed as a result.
333 public static boolean handleCommandLine(String[] args) {
335 // Check command-line for files
336 boolean opened = false;
337 for (String file : args) {
338 if (BasicFrame.open(new File(file), null)) {
348 * Check that the JRE is not running headless.
350 private static void checkHead() {
352 log.info("Checking for graphics head");
354 if (GraphicsEnvironment.isHeadless()) {
355 log.error("Application is headless.");
356 System.err.println();
357 System.err.println("OpenRocket cannot currently be run without the graphical " +
359 System.err.println();
366 /////////// Logging ///////////
368 private static void initializeLogging() {
369 DelegatorLogger delegator = new DelegatorLogger();
372 LogLevelBufferLogger buffer = new LogLevelBufferLogger(LOG_BUFFER_LENGTH);
373 delegator.addLogger(buffer);
375 // Check whether to log to stdout/stderr
376 PrintStreamLogger printer = new PrintStreamLogger();
377 boolean logout = setLogOutput(printer, System.out, System.getProperty(LOG_STDOUT_PROPERTY), null);
378 boolean logerr = setLogOutput(printer, System.err, System.getProperty(LOG_STDERR_PROPERTY), LogLevel.ERROR);
379 if (logout || logerr) {
380 delegator.addLogger(printer);
384 Application.setLogger(delegator);
385 Application.setLogBuffer(buffer);
387 // Initialize the log for this class
388 log = Application.getLogger();
389 log.info("Logging subsystem initialized for OpenRocket " + Prefs.getVersion());
390 String str = "Console logging output:";
391 for (LogLevel l : LogLevel.values()) {
392 PrintStream ps = printer.getOutput(l);
393 str += " " + l.name() + ":";
394 if (ps == System.err) {
396 } else if (ps == System.out) {
402 str += " (" + LOG_STDOUT_PROPERTY + "=" + System.getProperty(LOG_STDOUT_PROPERTY) +
403 " " + LOG_STDERR_PROPERTY + "=" + System.getProperty(LOG_STDERR_PROPERTY) + ")";
407 private static boolean setLogOutput(PrintStreamLogger logger, PrintStream stream, String level, LogLevel defaultLevel) {
408 LogLevel minLevel = LogLevel.fromString(level, defaultLevel);
409 if (minLevel == null) {
413 for (LogLevel l : LogLevel.values()) {
414 if (l.atLeast(minLevel)) {
415 logger.setOutput(l, stream);
422 /////////// Helper methods //////////
425 * Presents an error message to the user and exits the application.
427 * @param message an array of messages to present.
429 static void error(String[] message) {
431 System.err.println();
432 System.err.println("Error starting OpenRocket:");
433 System.err.println();
434 for (int i = 0; i < message.length; i++) {
435 System.err.println(message[i]);
437 System.err.println();
440 if (!GraphicsEnvironment.isHeadless()) {
442 JOptionPane.showMessageDialog(null, message, "Error starting OpenRocket",
443 JOptionPane.ERROR_MESSAGE);
452 * Presents the user with a message dialog and asks whether to continue.
453 * If the user does not select "Yes" the the application exits.
455 * @param message the message Strings to show.
457 static void confirm(String[] message) {
459 if (!GraphicsEnvironment.isHeadless()) {
461 if (JOptionPane.showConfirmDialog(null, message, "Error starting OpenRocket",
462 JOptionPane.YES_NO_OPTION) != JOptionPane.YES_OPTION) {