0a12605b63c556384304c045fb74b1ba6fa010f3
[debian/openrocket] / core / src / net / sf / openrocket / startup / ConcurrentLoadingThrustCurveMotorSetDatabase.java
1 package net.sf.openrocket.startup;
2
3 import java.io.File;
4 import java.io.IOException;
5 import java.io.InputStream;
6 import java.util.List;
7 import java.util.concurrent.ExecutorService;
8 import java.util.concurrent.Executors;
9 import java.util.concurrent.LinkedBlockingQueue;
10 import java.util.concurrent.ThreadFactory;
11 import java.util.concurrent.ThreadPoolExecutor;
12 import java.util.concurrent.TimeUnit;
13
14 import net.sf.openrocket.database.ThrustCurveMotorSet;
15 import net.sf.openrocket.database.ThrustCurveMotorSetDatabase;
16 import net.sf.openrocket.file.iterator.DirectoryIterator;
17 import net.sf.openrocket.file.iterator.FileIterator;
18 import net.sf.openrocket.file.motor.MotorLoaderHelper;
19 import net.sf.openrocket.gui.util.SimpleFileFilter;
20 import net.sf.openrocket.gui.util.SwingPreferences;
21 import net.sf.openrocket.logging.LogHelper;
22 import net.sf.openrocket.motor.Motor;
23 import net.sf.openrocket.motor.ThrustCurveMotor;
24 import net.sf.openrocket.util.BugException;
25 import net.sf.openrocket.util.Pair;
26
27 /**
28  * Load motors in parallel using a three stage pipeline.
29  * 
30  * Stage 1: single thread managed by the ThrustCurveMotorSetDatabase.  This thread generates
31  *          one object for each thrust curve motor file and puts it in the second stage.
32  *          
33  * Stage 2: multiple threads which process individual files.  Each process takes
34  *          a single motor file and parses out the list of motors it contains.
35  *          The list of motors is queued up for the third stage to process.
36  *          
37  * Stage 3: single thread which processes the list of motors generated in stage 2.
38  *          This thread puts all the motors from the list in the motor set database.
39  *          
40  * It is important that stage 3 be done with a single thread because ThrustCurveMotorSetDatabase
41  * is not thread safe.  Even if synchronization were to be done, it is unlikely that parallelizing
42  * this process would improve anything.
43  * 
44  *
45  */
46 public class ConcurrentLoadingThrustCurveMotorSetDatabase extends ThrustCurveMotorSetDatabase {
47
48         private static final LogHelper log = Application.getLogger();
49         private final String thrustCurveDirectory;
50
51
52         public ConcurrentLoadingThrustCurveMotorSetDatabase(String thrustCurveDirectory) {
53                 // configure ThrustCurveMotorSetDatabase as true so we get our own thread in
54                 // loadMotors.
55                 super(true);
56                 this.thrustCurveDirectory = thrustCurveDirectory;
57         }
58
59         @Override
60         protected void loadMotors() {
61
62                 BookKeeping keeper = new BookKeeping();
63                 keeper.start();
64
65                 try {
66                         keeper.waitForFinish();
67                 }
68                 catch ( InterruptedException iex ) {
69                         throw new BugException(iex);
70                 }
71
72                 keeper = null;
73
74         }
75
76         private void addAll( List<Motor> motors ) {
77                 for (Motor m : motors) {
78                         addMotor( (ThrustCurveMotor) m);
79                 }
80         }
81
82         /**
83          * A class which holds all the threading data.
84          * Implemented as an inner class so we can easily jettison the references when
85          * the processing is terminated.
86          *
87          */
88         private class BookKeeping {
89
90                 /*
91                  * Executor for Stage 3.
92                  */
93                 private final ExecutorService writerThread;
94
95                 /*
96                  * Executor for Stage 2.
97                  */
98                 private final ExecutorService loaderPool;
99
100                 /*
101                  * Runnable used for Stage 1.
102                  */
103                 private final WorkGenerator workGenerator;
104
105                 private long startTime;
106
107                 /*
108                  * Number of thrust curves loaded
109                  */
110                 private int thrustCurveCount = 0;
111
112                 /*
113                  * Number of files processed.
114                  */
115                 private int fileCount = 0;
116
117                 /*
118                  * We have to hold on to the zip file iterator which is used to load
119                  * the system motor files until all processing is done.  This is because
120                  * closing the iterator prematurely causes all the InputStreams opened
121                  * with it to close. 
122                  */
123                 private FileIterator iterator;
124
125                 private BookKeeping() {
126
127                         writerThread = Executors.newSingleThreadExecutor( new ThreadFactory() {
128                                 @Override
129                                 public Thread newThread(Runnable r) {
130                                         Thread t = new Thread(r,"MotorWriterThread");
131                                         return t;
132                                 }
133                         });
134
135                         loaderPool = new ThreadPoolExecutor(25,25, 2, TimeUnit.SECONDS,
136                                         new LinkedBlockingQueue<Runnable>(),
137                                         new ThreadFactory() {
138                                 int threadCount = 0;
139                                 @Override
140                                 public Thread newThread(Runnable r) {
141                                         Thread t = new Thread(r,"MotorLoaderPool-" + threadCount++);
142                                         return t;
143                                 }
144                         });
145
146                         workGenerator = new WorkGenerator();
147
148                 }
149
150                 private void start() {
151
152                         startTime = System.currentTimeMillis();
153
154                         log.info("Starting motor loading from " + thrustCurveDirectory + " in background thread.");
155
156                         // Run the work generator - in this thread.
157                         workGenerator.run();
158
159                 }
160
161                 private void waitForFinish() throws InterruptedException {
162                         try {
163                                 loaderPool.shutdown();
164                                 loaderPool.awaitTermination(10, TimeUnit.SECONDS);
165                                 writerThread.shutdown();
166                                 writerThread.awaitTermination(10, TimeUnit.SECONDS);
167                         }
168                         finally {
169                                 iterator.close();
170                         }
171
172                         long endTime = System.currentTimeMillis();
173
174                         int distinctMotorCount = 0;
175                         int distinctThrustCurveCount = 0;
176                         distinctMotorCount = motorSets.size();
177                         for (ThrustCurveMotorSet set : motorSets) {
178                                 distinctThrustCurveCount += set.getMotorCount();
179                         }
180
181                         log.info("Motor loading done, took " + (endTime - startTime) + " ms to load " 
182                                         + fileCount + " files/directories containing " 
183                                         + thrustCurveCount + " thrust curves which contained "
184                                         + distinctMotorCount + " distinct motors with "
185                                         + distinctThrustCurveCount + " distinct thrust curves.");
186
187                 }
188
189
190                 private class WorkGenerator implements Runnable {
191
192                         @Override
193                         public void run() {
194                                 // Start loading
195                                 log.info("Loading motors from " + thrustCurveDirectory);
196
197                                 iterator = DirectoryIterator.findDirectory(thrustCurveDirectory,
198                                                 new SimpleFileFilter("", false, "eng", "rse"));
199
200                                 // Load the packaged thrust curves
201                                 if (iterator == null) {
202                                         throw new IllegalStateException("Thrust curve directory " + thrustCurveDirectory +
203                                                         "not found, distribution built wrong");
204                                 }
205
206                                 while( iterator.hasNext() ) {
207                                         Pair<String,InputStream> f = iterator.next();
208                                         MotorLoader loader = new MotorLoader( f.getV(), f.getU() );
209                                         loaderPool.execute(loader);
210                                         fileCount ++;
211                                 }
212
213                                 // Load the user-defined thrust curves
214                                 for (File file : ((SwingPreferences) Application.getPreferences()).getUserThrustCurveFiles()) {
215                                         log.info("Loading motors from " + file);
216                                         MotorLoader loader = new MotorLoader( file );
217                                         loaderPool.execute(loader);
218                                         fileCount++;
219                                 }
220                         }
221                 }
222
223                 private class MotorLoader implements Runnable {
224
225                         private final InputStream is;
226                         private final String fileName;
227
228                         private final File file;
229
230                         public MotorLoader( File file ) {
231                                 super();
232                                 this.file = file;
233                                 this.is = null;
234                                 this.fileName = null;
235                         }
236
237                         public MotorLoader(InputStream is, String fileName) {
238                                 super();
239                                 this.file = null;
240                                 this.is = is;
241                                 this.fileName = fileName;
242                         }
243
244                         @Override
245                         public void run() {
246                                 log.debug("Loading motor from " + fileName);
247
248                                 try {
249                                         List<Motor> motors;
250                                         if ( file == null ) {
251                                                 motors = MotorLoaderHelper.load(is, fileName);
252                                         } else {
253                                                 motors = MotorLoaderHelper.load(file);
254                                         }
255                                         writerThread.submit( new MotorInserter(motors));
256                                 }
257                                 finally {
258                                         if ( is != null ) {
259                                                 try {
260                                                         is.close();
261                                                 } catch ( IOException iex ) {
262                                                 }
263                                         }
264                                 }
265                         }
266                 }
267
268                 private class MotorInserter implements Runnable {
269
270                         private final List<Motor> motors;
271
272                         MotorInserter( List<Motor> motors ) {
273                                 this.motors = motors;
274                         }
275
276                         @Override
277                         public void run() {
278                                 thrustCurveCount += motors.size();
279                                 ConcurrentLoadingThrustCurveMotorSetDatabase.this.addAll(motors);
280                         }
281
282                 }
283         }
284
285 }