Merge branch 'releases/Release_2_11_3_Branch'
[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
149       // 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     {// jalview.bin.Console.errPrintln("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     boolean working = false;
206     synchronized (inProgress)
207     {
208       // jalview.bin.Console.errPrintln("isWorking "+hashCode());
209       working |= inProgress.size() > 0;
210     }
211     synchronized (updating)
212     {
213       Collection<List<AlignCalcWorkerI>> workersLists = updating.values();
214       synchronized (workersLists)
215       {
216         for (List<AlignCalcWorkerI> workers : workersLists)
217         {
218           if (workers != null)
219           {
220             synchronized (workers)
221             {
222               working |= workers.size() > 0;
223             }
224           }
225         }
226       }
227     }
228     return working;
229   }
230
231   @Override
232   public void registerWorker(AlignCalcWorkerI worker)
233   {
234     synchronized (restartable)
235     {
236       if (!restartable.contains(worker))
237       {
238         restartable.add(worker);
239       }
240       startWorker(worker);
241     }
242   }
243
244   @Override
245   public void restartWorkers()
246   {
247     synchronized (restartable)
248     {
249       for (AlignCalcWorkerI worker : restartable)
250       {
251         startWorker(worker);
252       }
253     }
254   }
255
256   @Override
257   public boolean workingInvolvedWith(
258           AlignmentAnnotation alignmentAnnotation)
259   {
260     synchronized (inProgress)
261     {
262       for (AlignCalcWorkerI worker : inProgress)
263       {
264         if (worker.involves(alignmentAnnotation))
265         {
266           return true;
267         }
268       }
269     }
270     synchronized (updating)
271     {
272       for (List<AlignCalcWorkerI> workers : updating.values())
273       {
274         for (AlignCalcWorkerI worker : workers)
275         {
276           if (worker.involves(alignmentAnnotation))
277           {
278             return true;
279           }
280         }
281       }
282     }
283     return false;
284   }
285
286   @Override
287   public void updateAnnotationFor(
288           Class<? extends AlignCalcWorkerI> workerClass)
289   {
290
291     AlignCalcWorkerI[] workers;
292     synchronized (canUpdate)
293     {
294       workers = canUpdate.toArray(new AlignCalcWorkerI[0]);
295     }
296     for (AlignCalcWorkerI worker : workers)
297     {
298       if (workerClass.equals(worker.getClass()))
299       {
300         worker.updateAnnotation();
301       }
302     }
303   }
304
305   @Override
306   public List<AlignCalcWorkerI> getRegisteredWorkersOfClass(
307           Class<? extends AlignCalcWorkerI> workerClass)
308   {
309     List<AlignCalcWorkerI> workingClass = new ArrayList<AlignCalcWorkerI>();
310     AlignCalcWorkerI[] workers;
311     synchronized (canUpdate)
312     {
313       workers = canUpdate.toArray(new AlignCalcWorkerI[0]);
314     }
315     for (AlignCalcWorkerI worker : workers)
316     {
317       if (workerClass.equals(worker.getClass()))
318       {
319         workingClass.add(worker);
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<AlignCalcWorkerI>();
339     Set<AlignCalcWorkerI> toremovannot = new HashSet<AlignCalcWorkerI>();
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 { jalview.bin.Console.errPrintln("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<AlignCalcWorkerI>();
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 }