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