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 if (Locale.getDefault().getLanguage().equals("xx")) {
143 t = new DebugTranslator();
145 t = new ResourceBundleTranslator("l10n.messages");
148 log.info("Set up translation for locale " + Locale.getDefault() +
149 ", debug.currentFile=" + t.get("debug.currentFile"));
151 Application.setBaseTranslator(t);
156 private static void runMain(String[] args) {
158 // Initialize the splash screen with version info
159 log.info("Initializing the splash screen");
162 // Setup the uncaught exception handler
163 log.info("Registering exception handler");
164 ExceptionHandler.registerExceptionHandler();
166 // Start update info fetching
167 final UpdateInfoRetriever updateInfo;
168 if (Prefs.getCheckUpdates()) {
169 log.info("Starting update check");
170 updateInfo = new UpdateInfoRetriever();
173 log.info("Update check disabled");
177 // Set the best available look-and-feel
178 log.info("Setting best LAF");
179 GUIUtil.setBestLAF();
181 // Set tooltip delay time. Tooltips are used in MotorChooserDialog extensively.
182 ToolTipManager.sharedInstance().setDismissDelay(30000);
185 Prefs.loadDefaultUnits();
188 log.info("Loading databases");
190 Databases.fakeMethod();
192 // Starting action (load files or open new document)
193 log.info("Opening main application window");
194 if (!handleCommandLine(args)) {
195 BasicFrame.newAction();
198 // Check whether update info has been fetched or whether it needs more time
199 log.info("Checking update status");
200 checkUpdateStatus(updateInfo);
205 private static void loadMotor() {
207 log.info("Starting motor loading from " + THRUSTCURVE_DIRECTORY + " in background thread.");
208 ThrustCurveMotorSetDatabase db = new ThrustCurveMotorSetDatabase(true) {
211 protected void loadMotors() {
213 // Block loading until timeout occurs or database is taken into use
214 log.info("Blocking motor loading while starting up");
215 while (!inUse && blockLoading.addAndGet(-100) > 0) {
218 } catch (InterruptedException e) {
221 log.info("Blocking ended, inUse=" + inUse + " slowLoadingCount=" + blockLoading.get());
224 log.info("Loading motors from " + THRUSTCURVE_DIRECTORY);
225 long t0 = System.currentTimeMillis();
227 int thrustCurveCount;
229 // Load the packaged thrust curves
231 FileIterator iterator = DirectoryIterator.findDirectory(THRUSTCURVE_DIRECTORY,
232 new SimpleFileFilter("", false, "eng", "rse"));
233 if (iterator == null) {
234 throw new IllegalStateException("Thrust curve directory " + THRUSTCURVE_DIRECTORY +
235 "not found, distribution built wrong");
237 list = MotorLoaderHelper.load(iterator);
238 for (Motor m : list) {
239 this.addMotor((ThrustCurveMotor) m);
241 fileCount = iterator.getFileCount();
243 thrustCurveCount = list.size();
245 // Load the user-defined thrust curves
246 for (File file : Prefs.getUserThrustCurveFiles()) {
247 // TODO: LOW: This counts a directory as one file
248 log.info("Loading motors from " + file);
249 list = MotorLoaderHelper.load(file);
250 for (Motor m : list) {
251 this.addMotor((ThrustCurveMotor) m);
254 thrustCurveCount += list.size();
257 long t1 = System.currentTimeMillis();
260 int distinctMotorCount = 0;
261 int distinctThrustCurveCount = 0;
262 distinctMotorCount = motorSets.size();
263 for (ThrustCurveMotorSet set : motorSets) {
264 distinctThrustCurveCount += set.getMotorCount();
266 log.info("Motor loading done, took " + (t1 - t0) + " ms to load "
267 + fileCount + " files/directories containing "
268 + thrustCurveCount + " thrust curves which contained "
269 + distinctMotorCount + " distinct motors with "
270 + distinctThrustCurveCount + " distinct thrust curves.");
275 Application.setMotorSetDatabase(db);
280 private static void checkUpdateStatus(final UpdateInfoRetriever updateInfo) {
281 if (updateInfo == null)
285 if (!updateInfo.isRunning())
288 final Timer timer = new Timer(delay, null);
290 ActionListener listener = new ActionListener() {
291 private int count = 5;
294 public void actionPerformed(ActionEvent e) {
295 if (!updateInfo.isRunning()) {
298 String current = Prefs.getVersion();
299 String last = Prefs.getString(Prefs.LAST_UPDATE, "");
301 UpdateInfo info = updateInfo.getUpdateInfo();
302 if (info != null && info.getLatestVersion() != null &&
303 !current.equals(info.getLatestVersion()) &&
304 !last.equals(info.getLatestVersion())) {
306 UpdateInfoDialog infoDialog = new UpdateInfoDialog(info);
307 infoDialog.setVisible(true);
308 if (infoDialog.isReminderSelected()) {
309 Prefs.putString(Prefs.LAST_UPDATE, "");
311 Prefs.putString(Prefs.LAST_UPDATE, info.getLatestVersion());
320 timer.addActionListener(listener);
326 * Handles arguments passed from the command line. This may be used either
327 * when starting the first instance of OpenRocket or later when OpenRocket is
328 * executed again while running.
330 * @param args the command-line arguments.
331 * @return whether a new frame was opened or similar user desired action was
332 * performed as a result.
334 public static boolean handleCommandLine(String[] args) {
336 // Check command-line for files
337 boolean opened = false;
338 for (String file : args) {
339 if (BasicFrame.open(new File(file), null)) {
349 * Check that the JRE is not running headless.
351 private static void checkHead() {
353 log.info("Checking for graphics head");
355 if (GraphicsEnvironment.isHeadless()) {
356 log.error("Application is headless.");
357 System.err.println();
358 System.err.println("OpenRocket cannot currently be run without the graphical " +
360 System.err.println();
367 /////////// Logging ///////////
369 private static void initializeLogging() {
370 DelegatorLogger delegator = new DelegatorLogger();
373 LogLevelBufferLogger buffer = new LogLevelBufferLogger(LOG_BUFFER_LENGTH);
374 delegator.addLogger(buffer);
376 // Check whether to log to stdout/stderr
377 PrintStreamLogger printer = new PrintStreamLogger();
378 boolean logout = setLogOutput(printer, System.out, System.getProperty(LOG_STDOUT_PROPERTY), null);
379 boolean logerr = setLogOutput(printer, System.err, System.getProperty(LOG_STDERR_PROPERTY), LogLevel.ERROR);
380 if (logout || logerr) {
381 delegator.addLogger(printer);
385 Application.setLogger(delegator);
386 Application.setLogBuffer(buffer);
388 // Initialize the log for this class
389 log = Application.getLogger();
390 log.info("Logging subsystem initialized for OpenRocket " + Prefs.getVersion());
391 String str = "Console logging output:";
392 for (LogLevel l : LogLevel.values()) {
393 PrintStream ps = printer.getOutput(l);
394 str += " " + l.name() + ":";
395 if (ps == System.err) {
397 } else if (ps == System.out) {
403 str += " (" + LOG_STDOUT_PROPERTY + "=" + System.getProperty(LOG_STDOUT_PROPERTY) +
404 " " + LOG_STDERR_PROPERTY + "=" + System.getProperty(LOG_STDERR_PROPERTY) + ")";
408 private static boolean setLogOutput(PrintStreamLogger logger, PrintStream stream, String level, LogLevel defaultLevel) {
409 LogLevel minLevel = LogLevel.fromString(level, defaultLevel);
410 if (minLevel == null) {
414 for (LogLevel l : LogLevel.values()) {
415 if (l.atLeast(minLevel)) {
416 logger.setOutput(l, stream);
423 /////////// Helper methods //////////
426 * Presents an error message to the user and exits the application.
428 * @param message an array of messages to present.
430 static void error(String[] message) {
432 System.err.println();
433 System.err.println("Error starting OpenRocket:");
434 System.err.println();
435 for (int i = 0; i < message.length; i++) {
436 System.err.println(message[i]);
438 System.err.println();
441 if (!GraphicsEnvironment.isHeadless()) {
443 JOptionPane.showMessageDialog(null, message, "Error starting OpenRocket",
444 JOptionPane.ERROR_MESSAGE);
453 * Presents the user with a message dialog and asks whether to continue.
454 * If the user does not select "Yes" the the application exits.
456 * @param message the message Strings to show.
458 static void confirm(String[] message) {
460 if (!GraphicsEnvironment.isHeadless()) {
462 if (JOptionPane.showConfirmDialog(null, message, "Error starting OpenRocket",
463 JOptionPane.YES_NO_OPTION) != JOptionPane.YES_OPTION) {