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 System.setProperty("openrocket.log.stdout", "VBOSE");
108 System.setProperty("openrocket.log.tracelevel", "VBOSE");
109 System.setProperty("openrocket.debug.menu", "true");
110 System.setProperty("openrocket.debug.mutexlocation", "true");
111 System.setProperty("openrocket.debug.motordigest", "true");
117 * Initializes the localization system.
119 private static void initializeL10n() {
120 String locale = System.getProperty("openrocket.locale");
121 if (locale != null) {
123 String[] split = locale.split("[_-]", 3);
124 if (split.length == 1) {
125 l = new Locale(split[0]);
126 } else if (split.length == 2) {
127 l = new Locale(split[0], split[1]);
129 l = new Locale(split[0], split[1], split[2]);
131 Locale.setDefault(l);
135 if (Locale.getDefault().getLanguage().equals("xx")) {
136 t = new DebugTranslator();
138 t = new ResourceBundleTranslator("l10n.messages");
141 log.info("Set up translation for locale " + Locale.getDefault() +
142 ", debug.currentFile=" + t.get("debug.currentFile"));
144 Application.setTranslator(t);
149 private static void runMain(String[] args) {
151 // Initialize the splash screen with version info
152 log.info("Initializing the splash screen");
155 // Setup the uncaught exception handler
156 log.info("Registering exception handler");
157 ExceptionHandler.registerExceptionHandler();
159 // Start update info fetching
160 final UpdateInfoRetriever updateInfo;
161 if (Prefs.getCheckUpdates()) {
162 log.info("Starting update check");
163 updateInfo = new UpdateInfoRetriever();
166 log.info("Update check disabled");
170 // Set the best available look-and-feel
171 log.info("Setting best LAF");
172 GUIUtil.setBestLAF();
174 // Set tooltip delay time. Tooltips are used in MotorChooserDialog extensively.
175 ToolTipManager.sharedInstance().setDismissDelay(30000);
178 Prefs.loadDefaultUnits();
181 log.info("Loading databases");
183 Databases.fakeMethod();
185 // Starting action (load files or open new document)
186 log.info("Opening main application window");
187 if (!handleCommandLine(args)) {
188 BasicFrame.newAction();
191 // Check whether update info has been fetched or whether it needs more time
192 log.info("Checking update status");
193 checkUpdateStatus(updateInfo);
198 private static void loadMotor() {
200 log.info("Starting motor loading from " + THRUSTCURVE_DIRECTORY + " in background thread.");
201 ThrustCurveMotorSetDatabase db = new ThrustCurveMotorSetDatabase(true) {
204 protected void loadMotors() {
206 // Block loading until timeout occurs or database is taken into use
207 log.info("Blocking motor loading while starting up");
208 while (!inUse && blockLoading.addAndGet(-100) > 0) {
211 } catch (InterruptedException e) {
214 log.info("Blocking ended, inUse=" + inUse + " slowLoadingCount=" + blockLoading.get());
217 log.info("Loading motors from " + THRUSTCURVE_DIRECTORY);
218 long t0 = System.currentTimeMillis();
220 int thrustCurveCount;
222 // Load the packaged thrust curves
224 FileIterator iterator = DirectoryIterator.findDirectory(THRUSTCURVE_DIRECTORY,
225 new SimpleFileFilter("", false, "eng", "rse"));
226 if (iterator == null) {
227 throw new IllegalStateException("Thrust curve directory " + THRUSTCURVE_DIRECTORY +
228 "not found, distribution built wrong");
230 list = MotorLoaderHelper.load(iterator);
231 for (Motor m : list) {
232 this.addMotor((ThrustCurveMotor) m);
234 fileCount = iterator.getFileCount();
236 thrustCurveCount = list.size();
238 // Load the user-defined thrust curves
239 for (File file : Prefs.getUserThrustCurveFiles()) {
240 // TODO: LOW: This counts a directory as one file
241 log.info("Loading motors from " + file);
242 list = MotorLoaderHelper.load(file);
243 for (Motor m : list) {
244 this.addMotor((ThrustCurveMotor) m);
247 thrustCurveCount += list.size();
250 long t1 = System.currentTimeMillis();
253 int distinctMotorCount = 0;
254 int distinctThrustCurveCount = 0;
255 distinctMotorCount = motorSets.size();
256 for (ThrustCurveMotorSet set : motorSets) {
257 distinctThrustCurveCount += set.getMotorCount();
259 log.info("Motor loading done, took " + (t1 - t0) + " ms to load "
260 + fileCount + " files/directories containing "
261 + thrustCurveCount + " thrust curves which contained "
262 + distinctMotorCount + " distinct motors with "
263 + distinctThrustCurveCount + " distinct thrust curves.");
268 Application.setMotorSetDatabase(db);
273 private static void checkUpdateStatus(final UpdateInfoRetriever updateInfo) {
274 if (updateInfo == null)
278 if (!updateInfo.isRunning())
281 final Timer timer = new Timer(delay, null);
283 ActionListener listener = new ActionListener() {
284 private int count = 5;
287 public void actionPerformed(ActionEvent e) {
288 if (!updateInfo.isRunning()) {
291 String current = Prefs.getVersion();
292 String last = Prefs.getString(Prefs.LAST_UPDATE, "");
294 UpdateInfo info = updateInfo.getUpdateInfo();
295 if (info != null && info.getLatestVersion() != null &&
296 !current.equals(info.getLatestVersion()) &&
297 !last.equals(info.getLatestVersion())) {
299 UpdateInfoDialog infoDialog = new UpdateInfoDialog(info);
300 infoDialog.setVisible(true);
301 if (infoDialog.isReminderSelected()) {
302 Prefs.putString(Prefs.LAST_UPDATE, "");
304 Prefs.putString(Prefs.LAST_UPDATE, info.getLatestVersion());
313 timer.addActionListener(listener);
319 * Handles arguments passed from the command line. This may be used either
320 * when starting the first instance of OpenRocket or later when OpenRocket is
321 * executed again while running.
323 * @param args the command-line arguments.
324 * @return whether a new frame was opened or similar user desired action was
325 * performed as a result.
327 public static boolean handleCommandLine(String[] args) {
329 // Check command-line for files
330 boolean opened = false;
331 for (String file : args) {
332 if (BasicFrame.open(new File(file), null)) {
342 * Check that the JRE is not running headless.
344 private static void checkHead() {
346 log.info("Checking for graphics head");
348 if (GraphicsEnvironment.isHeadless()) {
349 log.error("Application is headless.");
350 System.err.println();
351 System.err.println("OpenRocket cannot currently be run without the graphical " +
353 System.err.println();
360 /////////// Logging ///////////
362 private static void initializeLogging() {
363 DelegatorLogger delegator = new DelegatorLogger();
366 LogLevelBufferLogger buffer = new LogLevelBufferLogger(LOG_BUFFER_LENGTH);
367 delegator.addLogger(buffer);
369 // Check whether to log to stdout/stderr
370 PrintStreamLogger printer = new PrintStreamLogger();
371 boolean logout = setLogOutput(printer, System.out, System.getProperty(LOG_STDOUT_PROPERTY), null);
372 boolean logerr = setLogOutput(printer, System.err, System.getProperty(LOG_STDERR_PROPERTY), LogLevel.ERROR);
373 if (logout || logerr) {
374 delegator.addLogger(printer);
378 Application.setLogger(delegator);
379 Application.setLogBuffer(buffer);
381 // Initialize the log for this class
382 log = Application.getLogger();
383 log.info("Logging subsystem initialized for OpenRocket " + Prefs.getVersion());
384 String str = "Console logging output:";
385 for (LogLevel l : LogLevel.values()) {
386 PrintStream ps = printer.getOutput(l);
387 str += " " + l.name() + ":";
388 if (ps == System.err) {
390 } else if (ps == System.out) {
396 str += " (" + LOG_STDOUT_PROPERTY + "=" + System.getProperty(LOG_STDOUT_PROPERTY) +
397 " " + LOG_STDERR_PROPERTY + "=" + System.getProperty(LOG_STDERR_PROPERTY) + ")";
401 private static boolean setLogOutput(PrintStreamLogger logger, PrintStream stream, String level, LogLevel defaultLevel) {
402 LogLevel minLevel = LogLevel.fromString(level, defaultLevel);
403 if (minLevel == null) {
407 for (LogLevel l : LogLevel.values()) {
408 if (l.atLeast(minLevel)) {
409 logger.setOutput(l, stream);
416 /////////// Helper methods //////////
419 * Presents an error message to the user and exits the application.
421 * @param message an array of messages to present.
423 static void error(String[] message) {
425 System.err.println();
426 System.err.println("Error starting OpenRocket:");
427 System.err.println();
428 for (int i = 0; i < message.length; i++) {
429 System.err.println(message[i]);
431 System.err.println();
434 if (!GraphicsEnvironment.isHeadless()) {
436 JOptionPane.showMessageDialog(null, message, "Error starting OpenRocket",
437 JOptionPane.ERROR_MESSAGE);
446 * Presents the user with a message dialog and asks whether to continue.
447 * If the user does not select "Yes" the the application exits.
449 * @param message the message Strings to show.
451 static void confirm(String[] message) {
453 if (!GraphicsEnvironment.isHeadless()) {
455 if (JOptionPane.showConfirmDialog(null, message, "Error starting OpenRocket",
456 JOptionPane.YES_NO_OPTION) != JOptionPane.YES_OPTION) {