JAL-3849 Remove join() which blocked the ui.
[jalview.git] / src / jalview / workers / AlignCalcManager.java
1 /*
2  * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
3  * Copyright (C) $$Year-Rel$$ The Jalview Authors
4  * 
5  * This file is part of Jalview.
6  * 
7  * Jalview is free software: you can redistribute it and/or
8  * modify it under the terms of the GNU General Public License 
9  * as published by the Free Software Foundation, either version 3
10  * of the License, or (at your option) any later version.
11  *  
12  * Jalview is distributed in the hope that it will be useful, but 
13  * WITHOUT ANY WARRANTY; without even the implied warranty 
14  * of MERCHANTABILITY or FITNESS FOR A PARTICULAR 
15  * PURPOSE.  See the GNU General Public License for more details.
16  * 
17  * You should have received a copy of the GNU General Public License
18  * along with Jalview.  If not, see <http://www.gnu.org/licenses/>.
19  * The Jalview Authors are detailed in the 'AUTHORS' file.
20  */
21 package jalview.workers;
22
23 import jalview.api.AlignCalcManagerI;
24 import jalview.api.AlignCalcWorkerI;
25 import jalview.bin.Cache;
26 import jalview.datamodel.AlignmentAnnotation;
27
28 import java.util.ArrayList;
29 import java.util.Collections;
30 import java.util.HashMap;
31 import java.util.HashSet;
32 import java.util.Hashtable;
33 import java.util.List;
34 import java.util.Map;
35 import java.util.Set;
36
37 public class AlignCalcManager implements AlignCalcManagerI
38 {
39   /*
40    * list of registered workers
41    */
42   private final List<AlignCalcWorkerI> restartable = Collections
43           .synchronizedList(new ArrayList<AlignCalcWorkerI>());
44
45   /*
46    * types of worker _not_ to run (for example, because they have
47    * previously thrown errors)
48    */
49   private final List<Class<? extends AlignCalcWorkerI>> blackList = Collections
50           .synchronizedList(new ArrayList<Class<? extends AlignCalcWorkerI>>());
51
52   /*
53    * global record of calculations in progress
54    */
55   private final List<AlignCalcWorkerI> inProgress = Collections
56           .synchronizedList(new ArrayList<AlignCalcWorkerI>());
57
58   /*
59    * record of calculations pending or in progress in the current context
60    */
61   private final Map<Class<? extends AlignCalcWorkerI>, List<AlignCalcWorkerI>> updating =
62           new Hashtable<Class<? extends AlignCalcWorkerI>, List<AlignCalcWorkerI>>();
63
64   /*
65    * workers that have run to completion so are candidates for visual-only 
66    * update of their results
67    */
68   private HashSet<AlignCalcWorkerI> canUpdate = new HashSet<>();;
69
70   private static boolean listContains(List<AlignCalcWorkerI> upd,
71           AlignCalcWorkerI worker)
72   {
73     // avoid use of 'Contains' in case
74     for (AlignCalcWorkerI _otherworker : upd)
75     {
76       if (_otherworker == upd)
77       {
78         return true;
79       }
80     }
81     return false;
82   }
83   @Override
84   public void notifyStarted(AlignCalcWorkerI worker)
85   {
86     synchronized (updating)
87     {
88       List<AlignCalcWorkerI> upd = updating.get(worker.getClass());
89       if (upd == null)
90       {
91         updating.put(worker.getClass(), upd = Collections
92                 .synchronizedList(new ArrayList<AlignCalcWorkerI>()));
93       }
94       synchronized (upd)
95       {
96         if (listContains(upd, worker))
97         {
98           Cache.log.debug(
99                     "Ignoring second call to notifyStart for worker "
100                             + worker);
101         }
102         else
103         {
104           upd.add(worker);
105         }
106       }
107     }
108   }
109
110   /*
111    * (non-Javadoc)
112    * 
113    * @see jalview.api.AlignCalcManagerI#isPending(jalview.api.AlignCalcWorkerI)
114    */
115   @Override
116   public boolean isPending(AlignCalcWorkerI workingClass)
117   {
118     synchronized (updating)
119     {
120       List<AlignCalcWorkerI> upd = updating.get(workingClass.getClass());
121       return upd != null && upd.size() > 1;
122     }
123   }
124
125   @Override
126   public boolean notifyWorking(AlignCalcWorkerI worker)
127   {
128     synchronized (inProgress)
129     {
130       if (listContains(inProgress, worker))
131       {
132         return false; // worker is already working, so ask caller to wait around
133       }
134       else
135       {
136         inProgress.add(worker);
137       }
138     }
139     return true;
140   }
141
142   @Override
143   public void workerComplete(AlignCalcWorkerI worker)
144   {
145     synchronized (inProgress)
146     {
147       Cache.log.debug("Worker " + worker + " marked as complete.");
148       inProgress.remove(worker);
149       List<AlignCalcWorkerI> upd = updating.get(worker.getClass());
150       if (upd != null)
151       {
152         synchronized (upd)
153         {
154           upd.remove(worker);
155         }
156         canUpdate.add(worker);
157       }
158     }
159   }
160
161   @Override
162   public void disableWorker(AlignCalcWorkerI worker)
163   {
164     synchronized (blackList)
165     {
166       blackList.add(worker.getClass());
167     }
168   }
169
170   @Override
171   public boolean isDisabled(AlignCalcWorkerI worker)
172   {
173     synchronized (blackList)
174     {
175       return blackList.contains(worker.getClass());
176     }
177   }
178
179   @Override
180   public void startWorker(AlignCalcWorkerI worker)
181   {
182     if (!isDisabled(worker))
183     {
184       Thread tw = new Thread(() -> {
185         try
186         {
187           worker.run();
188         } catch (Throwable e)
189         {
190           e.printStackTrace();
191         }
192       });
193       tw.setName(worker.getClass().toString());
194       tw.start();
195     }
196   }
197
198   @Override
199   public boolean isWorking(AlignCalcWorkerI worker)
200   {
201     synchronized (inProgress)
202     {// System.err.println("isWorking : worker "+(worker!=null ?
203      // worker.getClass():"null")+ " "+hashCode());
204       return worker != null && inProgress.contains(worker);
205     }
206   }
207
208   @Override
209   public boolean isWorking()
210   {
211     synchronized (inProgress)
212     {
213       // System.err.println("isWorking "+hashCode());
214       return inProgress.size() > 0;
215     }
216   }
217
218   public int getQueueLength() {
219     return inProgress.size();
220   }
221   
222   @Override
223   public void registerWorker(AlignCalcWorkerI worker)
224   {
225     synchronized (restartable)
226     {
227       if (!listContains(restartable, worker))
228       {
229         restartable.add(worker);
230       }
231       startWorker(worker);
232     }
233   }
234
235   @Override
236   public void restartWorkers()
237   {
238     synchronized (restartable)
239     {
240       for (AlignCalcWorkerI worker : restartable)
241       {
242         startWorker(worker);
243       }
244     }
245   }
246
247   @Override
248   public boolean workingInvolvedWith(
249           AlignmentAnnotation alignmentAnnotation)
250   {
251     synchronized (inProgress)
252     {
253       for (AlignCalcWorkerI worker : inProgress)
254       {
255         if (worker.involves(alignmentAnnotation))
256         {
257           return true;
258         }
259       }
260     }
261     synchronized (updating)
262     {
263       for (List<AlignCalcWorkerI> workers : updating.values())
264       {
265         for (AlignCalcWorkerI worker : workers)
266         {
267           if (worker.involves(alignmentAnnotation))
268           {
269             return true;
270           }
271         }
272       }
273     }
274     return false;
275   }
276
277   @Override
278   public void updateAnnotationFor(
279           Class<? extends AlignCalcWorkerI> workerClass)
280   {
281
282     AlignCalcWorkerI[] workers;
283     synchronized (canUpdate)
284     {
285       workers = canUpdate.toArray(new AlignCalcWorkerI[0]);
286     }
287     for (AlignCalcWorkerI worker : workers)
288     {
289       if (workerClass.equals(worker.getClass()))
290       {
291         worker.updateAnnotation();
292       }
293     }
294   }
295
296   @Override
297   public List<AlignCalcWorkerI> getRegisteredWorkersOfClass(
298           Class<? extends AlignCalcWorkerI> workerClass)
299   {
300     List<AlignCalcWorkerI> workingClass = new ArrayList<>();
301     synchronized (canUpdate)
302     {
303       for (AlignCalcWorkerI worker : canUpdate)
304       {
305         if (workerClass.equals(worker.getClass()))
306         {
307           workingClass.add(worker);
308         }
309       }
310     }
311     return (workingClass.size() == 0) ? null : workingClass;
312   }
313
314   @Override
315   public void enableWorker(AlignCalcWorkerI worker)
316   {
317     synchronized (blackList)
318     {
319       blackList.remove(worker.getClass());
320     }
321   }
322
323   @Override
324   public void removeWorkersOfClass(
325           Class<? extends AlignCalcWorkerI> typeToRemove)
326   {
327     List<AlignCalcWorkerI> removable = new ArrayList<>();
328     Set<AlignCalcWorkerI> toremovannot = new HashSet<>();
329     synchronized (restartable)
330     {
331       for (AlignCalcWorkerI worker : restartable)
332       {
333         if (typeToRemove.equals(worker.getClass()))
334         {
335           removable.add(worker);
336           toremovannot.add(worker);
337         }
338       }
339       restartable.removeAll(removable);
340     }
341     synchronized (canUpdate)
342     {
343       for (AlignCalcWorkerI worker : canUpdate)
344       {
345         if (typeToRemove.equals(worker.getClass()))
346         {
347           removable.add(worker);
348           toremovannot.add(worker);
349         }
350       }
351       canUpdate.removeAll(removable);
352     }
353     // TODO: finish testing this extension
354
355     /*
356      * synchronized (inProgress) { // need to kill or mark as dead any running
357      * threads... (inProgress.get(typeToRemove)); }
358      * 
359      * if (workers == null) { return; } for (AlignCalcWorkerI worker : workers)
360      * {
361      * 
362      * if (isPending(worker)) { worker.abortAndDestroy(); startWorker(worker); }
363      * else { System.err.println("Pending exists for " + workerClass); } }
364      */
365   }
366
367   /**
368    * Deletes the worker that update the given annotation, provided it is marked
369    * as deletable.
370    */
371   @Override
372   public void removeWorkerForAnnotation(AlignmentAnnotation ann)
373   {
374     /*
375      * first just find those to remove (to avoid
376      * ConcurrentModificationException)
377      */
378     List<AlignCalcWorkerI> toRemove = new ArrayList<>();
379     for (AlignCalcWorkerI worker : restartable)
380     {
381       if (worker.involves(ann))
382       {
383         if (worker.isDeletable())
384         {
385           toRemove.add(worker);
386         }
387       }
388     }
389
390     /*
391      * remove all references to deleted workers so any references 
392      * they hold to annotation data can be garbage collected 
393      */
394     for (AlignCalcWorkerI worker : toRemove)
395     {
396       restartable.remove(worker);
397       blackList.remove(worker.getClass());
398       inProgress.remove(worker);
399       canUpdate.remove(worker);
400       synchronized (updating)
401       {
402         List<AlignCalcWorkerI> upd = updating.get(worker.getClass());
403         if (upd != null)
404         {
405           upd.remove(worker);
406         }
407       }
408     }
409   }
410 }