JAL-4160 JAL-4195 wait around until alignFrame calculators are finished before trying...
[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.datamodel.AlignmentAnnotation;
26
27 import java.util.ArrayList;
28 import java.util.Collections;
29 import java.util.HashSet;
30 import java.util.Hashtable;
31 import java.util.List;
32 import java.util.Map;
33 import java.util.Set;
34
35 public class AlignCalcManager implements AlignCalcManagerI
36 {
37   /*
38    * list of registered workers
39    */
40   private volatile List<AlignCalcWorkerI> restartable;
41
42   /*
43    * types of worker _not_ to run (for example, because they have
44    * previously thrown errors)
45    */
46   private volatile List<Class<? extends AlignCalcWorkerI>> blackList;
47
48   /*
49    * global record of calculations in progress
50    */
51   private volatile List<AlignCalcWorkerI> inProgress;
52
53   /*
54    * record of calculations pending or in progress in the current context
55    */
56   private volatile Map<Class<? extends AlignCalcWorkerI>, List<AlignCalcWorkerI>> updating;
57
58   /*
59    * workers that have run to completion so are candidates for visual-only 
60    * update of their results
61    */
62   private HashSet<AlignCalcWorkerI> canUpdate;
63
64   /**
65    * Constructor
66    */
67   public AlignCalcManager()
68   {
69     restartable = Collections
70             .synchronizedList(new ArrayList<AlignCalcWorkerI>());
71     blackList = Collections.synchronizedList(
72             new ArrayList<Class<? extends AlignCalcWorkerI>>());
73     inProgress = Collections
74             .synchronizedList(new ArrayList<AlignCalcWorkerI>());
75     updating = Collections.synchronizedMap(
76             new Hashtable<Class<? extends AlignCalcWorkerI>, List<AlignCalcWorkerI>>());
77     canUpdate = new HashSet<AlignCalcWorkerI>();
78   }
79
80   @Override
81   public void notifyStart(AlignCalcWorkerI worker)
82   {
83     synchronized (updating)
84     {
85       List<AlignCalcWorkerI> upd = updating.get(worker.getClass());
86       if (upd == null)
87       {
88         updating.put(worker.getClass(), upd = Collections
89                 .synchronizedList(new ArrayList<AlignCalcWorkerI>()));
90       }
91       synchronized (upd)
92       {
93         upd.add(worker);
94       }
95     }
96   }
97
98   /*
99    * (non-Javadoc)
100    * 
101    * @see jalview.api.AlignCalcManagerI#isPending(jalview.api.AlignCalcWorkerI)
102    */
103   @Override
104   public boolean isPending(AlignCalcWorkerI workingClass)
105   {
106     List<AlignCalcWorkerI> upd;
107     synchronized (updating)
108     {
109       upd = updating.get(workingClass.getClass());
110       if (upd == null)
111       {
112         return false;
113       }
114       synchronized (upd)
115       {
116         if (upd.size() > 1)
117         {
118           return true;
119         }
120       }
121       return false;
122     }
123   }
124
125   @Override
126   public boolean notifyWorking(AlignCalcWorkerI worker)
127   {
128     synchronized (inProgress)
129     {
130       if (inProgress.contains(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       // System.err.println("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(worker);
185       tw.setName(worker.getClass().toString());
186       tw.start();
187     }
188   }
189
190   @Override
191   public boolean isWorking(AlignCalcWorkerI worker)
192   {
193     synchronized (inProgress)
194     {// System.err.println("isWorking : worker "+(worker!=null ?
195      // worker.getClass():"null")+ " "+hashCode());
196       return worker != null && inProgress.contains(worker);
197     }
198   }
199
200   @Override
201   public boolean isWorking()
202   {
203     boolean working=false;
204     synchronized (inProgress)
205     {
206       // System.err.println("isWorking "+hashCode());
207       working |= inProgress.size() > 0;
208     }
209     synchronized (updating)
210     {
211       for (List<AlignCalcWorkerI> workers : updating.values())
212       {
213         working |= workers.size() > 0;
214       }
215     }
216     return working;
217   }
218
219   @Override
220   public void registerWorker(AlignCalcWorkerI worker)
221   {
222     synchronized (restartable)
223     {
224       if (!restartable.contains(worker))
225       {
226         restartable.add(worker);
227       }
228       startWorker(worker);
229     }
230   }
231
232   @Override
233   public void restartWorkers()
234   {
235     synchronized (restartable)
236     {
237       for (AlignCalcWorkerI worker : restartable)
238       {
239         startWorker(worker);
240       }
241     }
242   }
243
244   @Override
245   public boolean workingInvolvedWith(
246           AlignmentAnnotation alignmentAnnotation)
247   {
248     synchronized (inProgress)
249     {
250       for (AlignCalcWorkerI worker : inProgress)
251       {
252         if (worker.involves(alignmentAnnotation))
253         {
254           return true;
255         }
256       }
257     }
258     synchronized (updating)
259     {
260       for (List<AlignCalcWorkerI> workers : updating.values())
261       {
262         for (AlignCalcWorkerI worker : workers)
263         {
264           if (worker.involves(alignmentAnnotation))
265           {
266             return true;
267           }
268         }
269       }
270     }
271     return false;
272   }
273
274   @Override
275   public void updateAnnotationFor(
276           Class<? extends AlignCalcWorkerI> workerClass)
277   {
278
279     AlignCalcWorkerI[] workers;
280     synchronized (canUpdate)
281     {
282       workers = canUpdate.toArray(new AlignCalcWorkerI[0]);
283     }
284     for (AlignCalcWorkerI worker : workers)
285     {
286       if (workerClass.equals(worker.getClass()))
287       {
288         worker.updateAnnotation();
289       }
290     }
291   }
292
293   @Override
294   public List<AlignCalcWorkerI> getRegisteredWorkersOfClass(
295           Class<? extends AlignCalcWorkerI> workerClass)
296   {
297     List<AlignCalcWorkerI> workingClass = new ArrayList<AlignCalcWorkerI>();
298     synchronized (canUpdate)
299     {
300       for (AlignCalcWorkerI worker : canUpdate)
301       {
302         if (workerClass.equals(worker.getClass()))
303         {
304           workingClass.add(worker);
305         }
306       }
307     }
308     return (workingClass.size() == 0) ? null : workingClass;
309   }
310
311   @Override
312   public void enableWorker(AlignCalcWorkerI worker)
313   {
314     synchronized (blackList)
315     {
316       blackList.remove(worker.getClass());
317     }
318   }
319
320   @Override
321   public void removeRegisteredWorkersOfClass(
322           Class<? extends AlignCalcWorkerI> typeToRemove)
323   {
324     List<AlignCalcWorkerI> removable = new ArrayList<AlignCalcWorkerI>();
325     Set<AlignCalcWorkerI> toremovannot = new HashSet<AlignCalcWorkerI>();
326     synchronized (restartable)
327     {
328       for (AlignCalcWorkerI worker : restartable)
329       {
330         if (typeToRemove.equals(worker.getClass()))
331         {
332           removable.add(worker);
333           toremovannot.add(worker);
334         }
335       }
336       restartable.removeAll(removable);
337     }
338     synchronized (canUpdate)
339     {
340       for (AlignCalcWorkerI worker : canUpdate)
341       {
342         if (typeToRemove.equals(worker.getClass()))
343         {
344           removable.add(worker);
345           toremovannot.add(worker);
346         }
347       }
348       canUpdate.removeAll(removable);
349     }
350     // TODO: finish testing this extension
351
352     /*
353      * synchronized (inProgress) { // need to kill or mark as dead any running
354      * threads... (inProgress.get(typeToRemove)); }
355      * 
356      * if (workers == null) { return; } for (AlignCalcWorkerI worker : workers)
357      * {
358      * 
359      * if (isPending(worker)) { worker.abortAndDestroy(); startWorker(worker); }
360      * else { System.err.println("Pending exists for " + workerClass); } }
361      */
362   }
363
364   /**
365    * Deletes the worker that update the given annotation, provided it is marked
366    * as deletable.
367    */
368   @Override
369   public void removeWorkerForAnnotation(AlignmentAnnotation ann)
370   {
371     /*
372      * first just find those to remove (to avoid
373      * ConcurrentModificationException)
374      */
375     List<AlignCalcWorkerI> toRemove = new ArrayList<AlignCalcWorkerI>();
376     for (AlignCalcWorkerI worker : restartable)
377     {
378       if (worker.involves(ann))
379       {
380         if (worker.isDeletable())
381         {
382           toRemove.add(worker);
383         }
384       }
385     }
386
387     /*
388      * remove all references to deleted workers so any references 
389      * they hold to annotation data can be garbage collected 
390      */
391     for (AlignCalcWorkerI worker : toRemove)
392     {
393       restartable.remove(worker);
394       blackList.remove(worker.getClass());
395       inProgress.remove(worker);
396       canUpdate.remove(worker);
397       synchronized (updating)
398       {
399         List<AlignCalcWorkerI> upd = updating.get(worker.getClass());
400         if (upd != null)
401         {
402           upd.remove(worker);
403         }
404       }
405     }
406   }
407 }