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