Merge branch 'Jalview-JS/develop.JAL-3446.ctrlDown' into
[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     synchronized (inProgress)
204     {
205       // System.err.println("isWorking "+hashCode());
206       return inProgress.size() > 0;
207     }
208   }
209
210   public int getQueueLength() {
211     return inProgress.size();
212   }
213   
214   @Override
215   public void registerWorker(AlignCalcWorkerI worker)
216   {
217     synchronized (restartable)
218     {
219       if (!restartable.contains(worker))
220       {
221         restartable.add(worker);
222       }
223       startWorker(worker);
224     }
225   }
226
227   @Override
228   public void restartWorkers()
229   {
230     synchronized (restartable)
231     {
232       for (AlignCalcWorkerI worker : restartable)
233       {
234         startWorker(worker);
235       }
236     }
237   }
238
239   @Override
240   public boolean workingInvolvedWith(
241           AlignmentAnnotation alignmentAnnotation)
242   {
243     synchronized (inProgress)
244     {
245       for (AlignCalcWorkerI worker : inProgress)
246       {
247         if (worker.involves(alignmentAnnotation))
248         {
249           return true;
250         }
251       }
252     }
253     synchronized (updating)
254     {
255       for (List<AlignCalcWorkerI> workers : updating.values())
256       {
257         for (AlignCalcWorkerI worker : workers)
258         {
259           if (worker.involves(alignmentAnnotation))
260           {
261             return true;
262           }
263         }
264       }
265     }
266     return false;
267   }
268
269   @Override
270   public void updateAnnotationFor(
271           Class<? extends AlignCalcWorkerI> workerClass)
272   {
273
274     AlignCalcWorkerI[] workers;
275     synchronized (canUpdate)
276     {
277       workers = canUpdate.toArray(new AlignCalcWorkerI[0]);
278     }
279     for (AlignCalcWorkerI worker : workers)
280     {
281       if (workerClass.equals(worker.getClass()))
282       {
283         worker.updateAnnotation();
284       }
285     }
286   }
287
288   @Override
289   public List<AlignCalcWorkerI> getRegisteredWorkersOfClass(
290           Class<? extends AlignCalcWorkerI> workerClass)
291   {
292     List<AlignCalcWorkerI> workingClass = new ArrayList<AlignCalcWorkerI>();
293     synchronized (canUpdate)
294     {
295       for (AlignCalcWorkerI worker : canUpdate)
296       {
297         if (workerClass.equals(worker.getClass()))
298         {
299           workingClass.add(worker);
300         }
301       }
302     }
303     return (workingClass.size() == 0) ? null : workingClass;
304   }
305
306   @Override
307   public void enableWorker(AlignCalcWorkerI worker)
308   {
309     synchronized (blackList)
310     {
311       blackList.remove(worker.getClass());
312     }
313   }
314
315   @Override
316   public void removeRegisteredWorkersOfClass(
317           Class<? extends AlignCalcWorkerI> typeToRemove)
318   {
319     List<AlignCalcWorkerI> removable = new ArrayList<AlignCalcWorkerI>();
320     Set<AlignCalcWorkerI> toremovannot = new HashSet<AlignCalcWorkerI>();
321     synchronized (restartable)
322     {
323       for (AlignCalcWorkerI worker : restartable)
324       {
325         if (typeToRemove.equals(worker.getClass()))
326         {
327           removable.add(worker);
328           toremovannot.add(worker);
329         }
330       }
331       restartable.removeAll(removable);
332     }
333     synchronized (canUpdate)
334     {
335       for (AlignCalcWorkerI worker : canUpdate)
336       {
337         if (typeToRemove.equals(worker.getClass()))
338         {
339           removable.add(worker);
340           toremovannot.add(worker);
341         }
342       }
343       canUpdate.removeAll(removable);
344     }
345     // TODO: finish testing this extension
346
347     /*
348      * synchronized (inProgress) { // need to kill or mark as dead any running
349      * threads... (inProgress.get(typeToRemove)); }
350      * 
351      * if (workers == null) { return; } for (AlignCalcWorkerI worker : workers)
352      * {
353      * 
354      * if (isPending(worker)) { worker.abortAndDestroy(); startWorker(worker); }
355      * else { System.err.println("Pending exists for " + workerClass); } }
356      */
357   }
358
359   /**
360    * Deletes the worker that update the given annotation, provided it is marked
361    * as deletable.
362    */
363   @Override
364   public void removeWorkerForAnnotation(AlignmentAnnotation ann)
365   {
366     /*
367      * first just find those to remove (to avoid
368      * ConcurrentModificationException)
369      */
370     List<AlignCalcWorkerI> toRemove = new ArrayList<AlignCalcWorkerI>();
371     for (AlignCalcWorkerI worker : restartable)
372     {
373       if (worker.involves(ann))
374       {
375         if (worker.isDeletable())
376         {
377           toRemove.add(worker);
378         }
379       }
380     }
381
382     /*
383      * remove all references to deleted workers so any references 
384      * they hold to annotation data can be garbage collected 
385      */
386     for (AlignCalcWorkerI worker : toRemove)
387     {
388       restartable.remove(worker);
389       blackList.remove(worker.getClass());
390       inProgress.remove(worker);
391       canUpdate.remove(worker);
392       synchronized (updating)
393       {
394         List<AlignCalcWorkerI> upd = updating.get(worker.getClass());
395         if (upd != null)
396         {
397           upd.remove(worker);
398         }
399       }
400     }
401   }
402 }