Multithread the orc loading process.
[debian/openrocket] / core / src / net / sf / openrocket / startup / Startup2.java
1 package net.sf.openrocket.startup;
2
3 import java.awt.GraphicsEnvironment;
4 import java.awt.event.ActionEvent;
5 import java.awt.event.ActionListener;
6 import java.io.File;
7 import java.util.List;
8 import java.util.concurrent.Callable;
9 import java.util.concurrent.CountDownLatch;
10 import java.util.concurrent.ExecutorService;
11 import java.util.concurrent.Executors;
12 import java.util.concurrent.ThreadFactory;
13 import java.util.concurrent.atomic.AtomicInteger;
14
15 import javax.swing.SwingUtilities;
16 import javax.swing.Timer;
17 import javax.swing.ToolTipManager;
18
19 import net.sf.openrocket.communication.UpdateInfo;
20 import net.sf.openrocket.communication.UpdateInfoRetriever;
21 import net.sf.openrocket.database.ComponentPresetDatabase;
22 import net.sf.openrocket.database.Databases;
23 import net.sf.openrocket.database.ThrustCurveMotorSet;
24 import net.sf.openrocket.database.ThrustCurveMotorSetDatabase;
25 import net.sf.openrocket.file.iterator.DirectoryIterator;
26 import net.sf.openrocket.file.iterator.FileIterator;
27 import net.sf.openrocket.file.motor.MotorLoaderHelper;
28 import net.sf.openrocket.gui.dialogs.UpdateInfoDialog;
29 import net.sf.openrocket.gui.main.BasicFrame;
30 import net.sf.openrocket.gui.main.Splash;
31 import net.sf.openrocket.gui.main.SwingExceptionHandler;
32 import net.sf.openrocket.gui.util.GUIUtil;
33 import net.sf.openrocket.gui.util.SimpleFileFilter;
34 import net.sf.openrocket.gui.util.SwingPreferences;
35 import net.sf.openrocket.logging.LogHelper;
36 import net.sf.openrocket.motor.Motor;
37 import net.sf.openrocket.motor.ThrustCurveMotor;
38 import net.sf.openrocket.util.BuildProperties;
39
40 /**
41  * The second class in the OpenRocket startup sequence.  This class can assume the
42  * Application class to be properly set up, and can use any classes safely.
43  * 
44  * @author Sampo Niskanen <sampo.niskanen@iki.fi>
45  */
46 public class Startup2 {
47         private static final LogHelper log = Application.getLogger();
48         
49
50         private static final String THRUSTCURVE_DIRECTORY = "datafiles/thrustcurves/";
51         
52         /** Block motor loading for this many milliseconds */
53         private static AtomicInteger blockLoading = new AtomicInteger(Integer.MAX_VALUE);
54         
55         
56
57         /**
58          * Run when starting up OpenRocket after Application has been set up.
59          * 
60          * @param args  command line arguments
61          */
62         static void runMain(final String[] args) throws Exception {
63                 
64                 log.info("Starting up OpenRocket version " + BuildProperties.getVersion());
65                 
66                 // Check that we're not running headless
67                 log.info("Checking for graphics head");
68                 checkHead();
69                 
70                 // Check that we're running a good version of a JRE
71                 log.info("Checking JRE compatibility");
72                 VersionHelper.checkVersion();
73                 VersionHelper.checkOpenJDK();
74                 
75                 // Run the actual startup method in the EDT since it can use progress dialogs etc.
76                 log.info("Moving startup to EDT");
77                 SwingUtilities.invokeAndWait(new Runnable() {
78                         @Override
79                         public void run() {
80                                 runInEDT(args);
81                         }
82                 });
83                 
84                 log.info("Startup complete");
85         }
86         
87         
88         /**
89          * Run in the EDT when starting up OpenRocket.
90          * 
91          * @param args  command line arguments
92          */
93         private static void runInEDT(String[] args) {
94                 
95                 // Initialize the splash screen with version info
96                 log.info("Initializing the splash screen");
97                 Splash.init();
98                 
99                 // Latch which counts the number of background loading processes we need to complete.
100                 CountDownLatch loading = new CountDownLatch(1);
101                 ExecutorService exec = Executors.newFixedThreadPool(2, new ThreadFactory() {
102
103                         @Override
104                         public Thread newThread(Runnable r) {
105                                 Thread t = new Thread(r);
106                                 t.setPriority(Thread.MIN_PRIORITY);
107                                 return t;
108                         }
109                         
110                 });
111
112                 // Must be done after localization is initialized
113                 ComponentPresetDatabase componentPresetDao = new ComponentPresetDatabase();
114                 exec.submit( new ComponentPresetLoader( loading, componentPresetDao));
115                 
116                 Application.setComponentPresetDao( componentPresetDao );
117                 
118                 // Setup the uncaught exception handler
119                 log.info("Registering exception handler");
120                 SwingExceptionHandler exceptionHandler = new SwingExceptionHandler();
121                 Application.setExceptionHandler(exceptionHandler);
122                 exceptionHandler.registerExceptionHandler();
123                 
124                 // Start update info fetching
125                 final UpdateInfoRetriever updateInfo;
126                 if ( Application.getPreferences().getCheckUpdates()) {
127                         log.info("Starting update check");
128                         updateInfo = new UpdateInfoRetriever();
129                         updateInfo.start();
130                 } else {
131                         log.info("Update check disabled");
132                         updateInfo = null;
133                 }
134                 
135                 // Set the best available look-and-feel
136                 log.info("Setting best LAF");
137                 GUIUtil.setBestLAF();
138                 
139                 // Set tooltip delay time.  Tooltips are used in MotorChooserDialog extensively.
140                 ToolTipManager.sharedInstance().setDismissDelay(30000);
141                 
142                 // Load defaults
143                 ((SwingPreferences) Application.getPreferences()).loadDefaultUnits();
144                 
145                 // Load motors etc.
146                 log.info("Loading databases");
147                 loadMotor();
148                 Databases.fakeMethod();
149                 
150                 try {
151                         loading.await();
152                 } catch ( InterruptedException iex) {
153                         
154                 }
155
156                 // Starting action (load files or open new document)
157                 log.info("Opening main application window");
158                 if (!handleCommandLine(args)) {
159                         BasicFrame.newAction();
160                 }
161                 
162                 // Check whether update info has been fetched or whether it needs more time
163                 log.info("Checking update status");
164                 checkUpdateStatus(updateInfo);
165                 
166                 // Block motor loading for 1.5 seconds to allow window painting to be faster
167                 blockLoading.set(1500);
168         }
169         
170         
171         /**
172          * Check that the JRE is not running headless.
173          */
174         private static void checkHead() {
175                 
176                 if (GraphicsEnvironment.isHeadless()) {
177                         log.error("Application is headless.");
178                         System.err.println();
179                         System.err.println("OpenRocket cannot currently be run without the graphical " +
180                                         "user interface.");
181                         System.err.println();
182                         System.exit(1);
183                 }
184                 
185         }
186         
187         
188         private static void loadMotor() {
189                 
190                 log.info("Starting motor loading from " + THRUSTCURVE_DIRECTORY + " in background thread.");
191                 ThrustCurveMotorSetDatabase db = new ThrustCurveMotorSetDatabase(true) {
192                         
193                         @Override
194                         protected void loadMotors() {
195                                 
196                                 // Block loading until timeout occurs or database is taken into use
197                                 log.info("Blocking motor loading while starting up");
198                                 while (!inUse && blockLoading.addAndGet(-100) > 0) {
199                                         try {
200                                                 Thread.sleep(100);
201                                         } catch (InterruptedException e) {
202                                         }
203                                 }
204                                 log.info("Blocking ended, inUse=" + inUse + " blockLoading=" + blockLoading.get());
205                                 
206                                 // Start loading
207                                 log.info("Loading motors from " + THRUSTCURVE_DIRECTORY);
208                                 long t0 = System.currentTimeMillis();
209                                 int fileCount;
210                                 int thrustCurveCount;
211                                 
212                                 // Load the packaged thrust curves
213                                 List<Motor> list;
214                                 FileIterator iterator = DirectoryIterator.findDirectory(THRUSTCURVE_DIRECTORY,
215                                                                 new SimpleFileFilter("", false, "eng", "rse"));
216                                 if (iterator == null) {
217                                         throw new IllegalStateException("Thrust curve directory " + THRUSTCURVE_DIRECTORY +
218                                                         "not found, distribution built wrong");
219                                 }
220                                 list = MotorLoaderHelper.load(iterator);
221                                 for (Motor m : list) {
222                                         this.addMotor((ThrustCurveMotor) m);
223                                 }
224                                 fileCount = iterator.getFileCount();
225                                 
226                                 thrustCurveCount = list.size();
227                                 
228                                 // Load the user-defined thrust curves
229                                 for (File file : ((SwingPreferences) Application.getPreferences()).getUserThrustCurveFiles()) {
230                                         log.info("Loading motors from " + file);
231                                         list = MotorLoaderHelper.load(file);
232                                         for (Motor m : list) {
233                                                 this.addMotor((ThrustCurveMotor) m);
234                                         }
235                                         fileCount++;
236                                         thrustCurveCount += list.size();
237                                 }
238                                 
239                                 long t1 = System.currentTimeMillis();
240                                 
241                                 // Count statistics
242                                 int distinctMotorCount = 0;
243                                 int distinctThrustCurveCount = 0;
244                                 distinctMotorCount = motorSets.size();
245                                 for (ThrustCurveMotorSet set : motorSets) {
246                                         distinctThrustCurveCount += set.getMotorCount();
247                                 }
248                                 log.info("Motor loading done, took " + (t1 - t0) + " ms to load "
249                                                 + fileCount + " files/directories containing "
250                                                 + thrustCurveCount + " thrust curves which contained "
251                                                 + distinctMotorCount + " distinct motors with "
252                                                 + distinctThrustCurveCount + " distinct thrust curves.");
253                         }
254                         
255                 };
256                 db.startLoading();
257                 Application.setMotorSetDatabase(db);
258         }
259         
260         private static void checkUpdateStatus(final UpdateInfoRetriever updateInfo) {
261                 if (updateInfo == null)
262                         return;
263                 
264                 int delay = 1000;
265                 if (!updateInfo.isRunning())
266                         delay = 100;
267                 
268                 final Timer timer = new Timer(delay, null);
269                 
270                 ActionListener listener = new ActionListener() {
271                         private int count = 5;
272                         
273                         @Override
274                         public void actionPerformed(ActionEvent e) {
275                                 if (!updateInfo.isRunning()) {
276                                         timer.stop();
277                                         
278                                         String current = BuildProperties.getVersion();
279                                         String last = Application.getPreferences().getString(Preferences.LAST_UPDATE, "");
280                                         
281                                         UpdateInfo info = updateInfo.getUpdateInfo();
282                                         if (info != null && info.getLatestVersion() != null &&
283                                                         !current.equals(info.getLatestVersion()) &&
284                                                         !last.equals(info.getLatestVersion())) {
285                                                 
286                                                 UpdateInfoDialog infoDialog = new UpdateInfoDialog(info);
287                                                 infoDialog.setVisible(true);
288                                                 if (infoDialog.isReminderSelected()) {
289                                                         Application.getPreferences().putString(Preferences.LAST_UPDATE, "");
290                                                 } else {
291                                                         Application.getPreferences().putString(Preferences.LAST_UPDATE, info.getLatestVersion());
292                                                 }
293                                         }
294                                 }
295                                 count--;
296                                 if (count <= 0)
297                                         timer.stop();
298                         }
299                 };
300                 timer.addActionListener(listener);
301                 timer.start();
302         }
303         
304         private static class ComponentPresetLoader implements Callable {
305
306                 CountDownLatch latch;
307                 ComponentPresetDatabase componentPresetDao;
308                 
309                 private ComponentPresetLoader( CountDownLatch latch, ComponentPresetDatabase componentPresetDao ) {
310                         this.componentPresetDao = componentPresetDao;
311                         this.latch = latch;
312                 }
313                 
314                 @Override
315                 public Object call() throws Exception {
316                         componentPresetDao.load("datafiles/presets", "(?i).*orc");
317                         latch.countDown();
318                         return null;
319                 }
320
321         }
322         
323         /**
324          * Handles arguments passed from the command line.  This may be used either
325          * when starting the first instance of OpenRocket or later when OpenRocket is
326          * executed again while running.
327          * 
328          * @param args  the command-line arguments.
329          * @return              whether a new frame was opened or similar user desired action was
330          *                              performed as a result.
331          */
332         private static boolean handleCommandLine(String[] args) {
333                 
334                 // Check command-line for files
335                 boolean opened = false;
336                 for (String file : args) {
337                         if (BasicFrame.open(new File(file), null)) {
338                                 opened = true;
339                         }
340                 }
341                 return opened;
342         }
343         
344 }