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