JAL-2587 prevented double overview recalc on opening overview
[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
72             .synchronizedList(new ArrayList<Class<? extends AlignCalcWorkerI>>());
73     inProgress = Collections
74             .synchronizedList(new ArrayList<AlignCalcWorkerI>());
75     updating = Collections
76             .synchronizedMap(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(
89                 worker.getClass(),
90                 upd = Collections
91                         .synchronizedList(new ArrayList<AlignCalcWorkerI>()));
92       }
93       synchronized (upd)
94       {
95         upd.add(worker);
96       }
97     }
98   }
99
100   /*
101    * (non-Javadoc)
102    * 
103    * @see jalview.api.AlignCalcManagerI#isPending(jalview.api.AlignCalcWorkerI)
104    */
105   @Override
106   public boolean isPending(AlignCalcWorkerI workingClass)
107   {
108     List<AlignCalcWorkerI> upd;
109     synchronized (updating)
110     {
111       upd = updating.get(workingClass.getClass());
112       if (upd == null)
113       {
114         return false;
115       }
116       synchronized (upd)
117       {
118         if (upd.size() > 1)
119         {
120           return true;
121         }
122       }
123       return false;
124     }
125   }
126
127   @Override
128   public boolean notifyWorking(AlignCalcWorkerI worker)
129   {
130     synchronized (inProgress)
131     {
132       if (inProgress.contains(worker))
133       {
134         return false; // worker is already working, so ask caller to wait around
135       }
136       else
137       {
138         inProgress.add(worker);
139       }
140     }
141     return true;
142   }
143
144   @Override
145   public void workerComplete(AlignCalcWorkerI worker)
146   {
147     synchronized (inProgress)
148     {
149       // System.err.println("Worker " + worker + " marked as complete.");
150       inProgress.remove(worker);
151       List<AlignCalcWorkerI> upd = updating.get(worker.getClass());
152       if (upd != null)
153       {
154         synchronized (upd)
155         {
156           upd.remove(worker);
157         }
158         canUpdate.add(worker);
159       }
160     }
161   }
162
163   @Override
164   public void disableWorker(AlignCalcWorkerI worker)
165   {
166     synchronized (blackList)
167     {
168       blackList.add(worker.getClass());
169     }
170   }
171
172   @Override
173   public boolean isDisabled(AlignCalcWorkerI worker)
174   {
175     synchronized (blackList)
176     {
177       return blackList.contains(worker.getClass());
178     }
179   }
180
181   @Override
182   public void startWorker(AlignCalcWorkerI worker)
183   {
184     if (!isDisabled(worker))
185     {
186       Thread tw = new Thread(worker);
187       tw.setName(worker.getClass().toString());
188       tw.start();
189     }
190   }
191
192   @Override
193   public boolean isWorking(AlignCalcWorkerI worker)
194   {
195     synchronized (inProgress)
196     {// System.err.println("isWorking : worker "+(worker!=null ?
197      // worker.getClass():"null")+ " "+hashCode());
198       return worker != null && inProgress.contains(worker);
199     }
200   }
201
202   @Override
203   public boolean isWorking()
204   {
205     synchronized (inProgress)
206     {
207       // System.err.println("isWorking "+hashCode());
208       return inProgress.size() > 0;
209     }
210   }
211
212   @Override
213   public void registerWorker(AlignCalcWorkerI worker)
214   {
215     synchronized (restartable)
216     {
217       if (!restartable.contains(worker))
218       {
219         restartable.add(worker);
220       }
221       startWorker(worker);
222     }
223   }
224
225   @Override
226   public void restartWorkers()
227   {
228     synchronized (restartable)
229     {
230       for (AlignCalcWorkerI worker : restartable)
231       {
232         startWorker(worker);
233       }
234     }
235   }
236
237   @Override
238   public boolean workingInvolvedWith(AlignmentAnnotation alignmentAnnotation)
239   {
240     synchronized (inProgress)
241     {
242       for (AlignCalcWorkerI worker : inProgress)
243       {
244         if (worker.involves(alignmentAnnotation))
245         {
246           return true;
247         }
248       }
249     }
250     synchronized (updating)
251     {
252       for (List<AlignCalcWorkerI> workers : updating.values())
253       {
254         for (AlignCalcWorkerI worker : workers)
255         {
256           if (worker.involves(alignmentAnnotation))
257           {
258             return true;
259           }
260         }
261       }
262     }
263     return false;
264   }
265
266   @Override
267   public void updateAnnotationFor(
268           Class<? extends AlignCalcWorkerI> workerClass)
269   {
270
271     AlignCalcWorkerI[] workers;
272     synchronized (canUpdate)
273     {
274       workers = canUpdate.toArray(new AlignCalcWorkerI[0]);
275     }
276     for (AlignCalcWorkerI worker : workers)
277     {
278       if (workerClass.equals(worker.getClass()))
279       {
280         worker.updateAnnotation();
281       }
282     }
283   }
284
285   @Override
286   public List<AlignCalcWorkerI> getRegisteredWorkersOfClass(
287           Class<? extends AlignCalcWorkerI> workerClass)
288   {
289     List<AlignCalcWorkerI> workingClass = new ArrayList<AlignCalcWorkerI>();
290     synchronized (canUpdate)
291     {
292       for (AlignCalcWorkerI worker : canUpdate)
293       {
294         if (workerClass.equals(worker.getClass()))
295         {
296           workingClass.add(worker);
297         }
298       }
299     }
300     return (workingClass.size() == 0) ? null : workingClass;
301   }
302
303   @Override
304   public void enableWorker(AlignCalcWorkerI worker)
305   {
306     synchronized (blackList)
307     {
308       blackList.remove(worker.getClass());
309     }
310   }
311
312   @Override
313   public void removeRegisteredWorkersOfClass(
314           Class<? extends AlignCalcWorkerI> typeToRemove)
315   {
316     List<AlignCalcWorkerI> removable = new ArrayList<AlignCalcWorkerI>();
317     Set<AlignCalcWorkerI> toremovannot = new HashSet<AlignCalcWorkerI>();
318     synchronized (restartable)
319     {
320       for (AlignCalcWorkerI worker : restartable)
321       {
322         if (typeToRemove.equals(worker.getClass()))
323         {
324           removable.add(worker);
325           toremovannot.add(worker);
326         }
327       }
328       restartable.removeAll(removable);
329     }
330     synchronized (canUpdate)
331     {
332       for (AlignCalcWorkerI worker : canUpdate)
333       {
334         if (typeToRemove.equals(worker.getClass()))
335         {
336           removable.add(worker);
337           toremovannot.add(worker);
338         }
339       }
340       canUpdate.removeAll(removable);
341     }
342     // TODO: finish testing this extension
343
344     /*
345      * synchronized (inProgress) { // need to kill or mark as dead any running
346      * threads... (inProgress.get(typeToRemove)); }
347      * 
348      * if (workers == null) { return; } for (AlignCalcWorkerI worker : workers)
349      * {
350      * 
351      * if (isPending(worker)) { worker.abortAndDestroy(); startWorker(worker); }
352      * else { System.err.println("Pending exists for " + workerClass); } }
353      */
354   }
355
356   /**
357    * Deletes the worker that update the given annotation, provided it is marked
358    * as deletable.
359    */
360   @Override
361   public void removeWorkerForAnnotation(AlignmentAnnotation ann)
362   {
363     /*
364      * first just find those to remove (to avoid
365      * ConcurrentModificationException)
366      */
367     List<AlignCalcWorkerI> toRemove = new ArrayList<AlignCalcWorkerI>();
368     for (AlignCalcWorkerI worker : restartable)
369     {
370       if (worker.involves(ann))
371       {
372         if (worker.isDeletable())
373         {
374           toRemove.add(worker);
375         }
376       }
377     }
378
379     /*
380      * remove all references to deleted workers so any references 
381      * they hold to annotation data can be garbage collected 
382      */
383     for (AlignCalcWorkerI worker : toRemove)
384     {
385       restartable.remove(worker);
386       blackList.remove(worker.getClass());
387       inProgress.remove(worker);
388       canUpdate.remove(worker);
389       synchronized (updating)
390       {
391         List<AlignCalcWorkerI> upd = updating.get(worker.getClass());
392         if (upd != null)
393         {
394           upd.remove(worker);
395         }
396       }
397     }
398   }
399 }