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