JAL-3690 separate startup and poll code in SeqAnnotationCalcWorker
[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.HashMap;
31 import java.util.HashSet;
32 import java.util.Hashtable;
33 import java.util.List;
34 import java.util.Map;
35 import java.util.Set;
36
37 public class AlignCalcManager implements AlignCalcManagerI
38 {
39   /*
40    * list of registered workers
41    */
42   private final List<AlignCalcWorkerI> restartable = Collections
43           .synchronizedList(new ArrayList<AlignCalcWorkerI>());
44
45   /*
46    * types of worker _not_ to run (for example, because they have
47    * previously thrown errors)
48    */
49   private final List<Class<? extends AlignCalcWorkerI>> blackList = Collections
50           .synchronizedList(new ArrayList<Class<? extends AlignCalcWorkerI>>());
51
52   /*
53    * global record of calculations in progress
54    */
55   private final List<AlignCalcWorkerI> inProgress = Collections
56           .synchronizedList(new ArrayList<AlignCalcWorkerI>());
57
58   /*
59    * record of calculations pending or in progress in the current context
60    */
61   private final Map<Class<? extends AlignCalcWorkerI>, List<AlignCalcWorkerI>> updating =
62           new Hashtable<Class<? extends AlignCalcWorkerI>, List<AlignCalcWorkerI>>();
63
64   /*
65    * workers that have run to completion so are candidates for visual-only 
66    * update of their results
67    */
68   private HashSet<AlignCalcWorkerI> canUpdate = new HashSet<>();;
69
70   private static boolean listContains(List<AlignCalcWorkerI> upd,
71           AlignCalcWorkerI worker)
72   {
73     // avoid use of 'Contains' in case
74     for (AlignCalcWorkerI _otherworker : upd)
75     {
76       if (_otherworker == upd)
77       {
78         return true;
79       }
80     }
81     return false;
82   }
83   @Override
84   public void notifyStarted(AlignCalcWorkerI worker)
85   {
86     synchronized (updating)
87     {
88       List<AlignCalcWorkerI> upd = updating.get(worker.getClass());
89       if (upd == null)
90       {
91         updating.put(worker.getClass(), upd = Collections
92                 .synchronizedList(new ArrayList<AlignCalcWorkerI>()));
93       }
94       synchronized (upd)
95       {
96         if (listContains(upd, worker))
97         {
98           Cache.log.debug(
99                     "Ignoring second call to notifyStart for worker "
100                             + worker);
101         }
102         else
103         {
104           upd.add(worker);
105         }
106       }
107     }
108   }
109
110   /*
111    * (non-Javadoc)
112    * 
113    * @see jalview.api.AlignCalcManagerI#isPending(jalview.api.AlignCalcWorkerI)
114    */
115   @Override
116   public boolean isPending(AlignCalcWorkerI workingClass)
117   {
118     synchronized (updating)
119     {
120       List<AlignCalcWorkerI> upd = updating.get(workingClass.getClass());
121       return upd != null && upd.size() > 1;
122     }
123   }
124
125   @Override
126   public boolean notifyWorking(AlignCalcWorkerI worker)
127   {
128     synchronized (inProgress)
129     {
130       if (listContains(inProgress, 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       Cache.log.debug("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(() -> {
185         try
186         {
187           worker.run();
188         } catch (Throwable e)
189         {
190           e.printStackTrace();
191         }
192       });
193       tw.setName(worker.getClass().toString());
194       tw.start();
195     }
196   }
197
198   @Override
199   public boolean isWorking(AlignCalcWorkerI worker)
200   {
201     synchronized (inProgress)
202     {// System.err.println("isWorking : worker "+(worker!=null ?
203      // worker.getClass():"null")+ " "+hashCode());
204       return worker != null && inProgress.contains(worker);
205     }
206   }
207
208   @Override
209   public boolean isWorking()
210   {
211     synchronized (inProgress)
212     {
213       // System.err.println("isWorking "+hashCode());
214       return inProgress.size() > 0;
215     }
216   }
217
218   @Override
219   public void registerWorker(AlignCalcWorkerI worker)
220   {
221     synchronized (restartable)
222     {
223       if (!listContains(restartable, worker))
224       {
225         restartable.add(worker);
226       }
227       startWorker(worker);
228     }
229   }
230
231   @Override
232   public void restartWorkers()
233   {
234     synchronized (restartable)
235     {
236       for (AlignCalcWorkerI worker : restartable)
237       {
238         startWorker(worker);
239       }
240     }
241   }
242
243   @Override
244   public boolean workingInvolvedWith(
245           AlignmentAnnotation alignmentAnnotation)
246   {
247     synchronized (inProgress)
248     {
249       for (AlignCalcWorkerI worker : inProgress)
250       {
251         if (worker.involves(alignmentAnnotation))
252         {
253           return true;
254         }
255       }
256     }
257     synchronized (updating)
258     {
259       for (List<AlignCalcWorkerI> workers : updating.values())
260       {
261         for (AlignCalcWorkerI worker : workers)
262         {
263           if (worker.involves(alignmentAnnotation))
264           {
265             return true;
266           }
267         }
268       }
269     }
270     return false;
271   }
272
273   @Override
274   public void updateAnnotationFor(
275           Class<? extends AlignCalcWorkerI> workerClass)
276   {
277
278     AlignCalcWorkerI[] workers;
279     synchronized (canUpdate)
280     {
281       workers = canUpdate.toArray(new AlignCalcWorkerI[0]);
282     }
283     for (AlignCalcWorkerI worker : workers)
284     {
285       if (workerClass.equals(worker.getClass()))
286       {
287         worker.updateAnnotation();
288       }
289     }
290   }
291
292   @Override
293   public List<AlignCalcWorkerI> getRegisteredWorkersOfClass(
294           Class<? extends AlignCalcWorkerI> workerClass)
295   {
296     List<AlignCalcWorkerI> workingClass = new ArrayList<>();
297     synchronized (canUpdate)
298     {
299       for (AlignCalcWorkerI worker : canUpdate)
300       {
301         if (workerClass.equals(worker.getClass()))
302         {
303           workingClass.add(worker);
304         }
305       }
306     }
307     return (workingClass.size() == 0) ? null : workingClass;
308   }
309
310   @Override
311   public void enableWorker(AlignCalcWorkerI worker)
312   {
313     synchronized (blackList)
314     {
315       blackList.remove(worker.getClass());
316     }
317   }
318
319   @Override
320   public void removeWorkersOfClass(
321           Class<? extends AlignCalcWorkerI> typeToRemove)
322   {
323     List<AlignCalcWorkerI> removable = new ArrayList<>();
324     Set<AlignCalcWorkerI> toremovannot = new HashSet<>();
325     synchronized (restartable)
326     {
327       for (AlignCalcWorkerI worker : restartable)
328       {
329         if (typeToRemove.equals(worker.getClass()))
330         {
331           removable.add(worker);
332           toremovannot.add(worker);
333         }
334       }
335       restartable.removeAll(removable);
336     }
337     synchronized (canUpdate)
338     {
339       for (AlignCalcWorkerI worker : canUpdate)
340       {
341         if (typeToRemove.equals(worker.getClass()))
342         {
343           removable.add(worker);
344           toremovannot.add(worker);
345         }
346       }
347       canUpdate.removeAll(removable);
348     }
349     // TODO: finish testing this extension
350
351     /*
352      * synchronized (inProgress) { // need to kill or mark as dead any running
353      * threads... (inProgress.get(typeToRemove)); }
354      * 
355      * if (workers == null) { return; } for (AlignCalcWorkerI worker : workers)
356      * {
357      * 
358      * if (isPending(worker)) { worker.abortAndDestroy(); startWorker(worker); }
359      * else { System.err.println("Pending exists for " + workerClass); } }
360      */
361   }
362
363   /**
364    * Deletes the worker that update the given annotation, provided it is marked
365    * as deletable.
366    */
367   @Override
368   public void removeWorkerForAnnotation(AlignmentAnnotation ann)
369   {
370     /*
371      * first just find those to remove (to avoid
372      * ConcurrentModificationException)
373      */
374     List<AlignCalcWorkerI> toRemove = new ArrayList<>();
375     for (AlignCalcWorkerI worker : restartable)
376     {
377       if (worker.involves(ann))
378       {
379         if (worker.isDeletable())
380         {
381           toRemove.add(worker);
382         }
383       }
384     }
385
386     /*
387      * remove all references to deleted workers so any references 
388      * they hold to annotation data can be garbage collected 
389      */
390     for (AlignCalcWorkerI worker : toRemove)
391     {
392       restartable.remove(worker);
393       blackList.remove(worker.getClass());
394       inProgress.remove(worker);
395       canUpdate.remove(worker);
396       synchronized (updating)
397       {
398         List<AlignCalcWorkerI> upd = updating.get(worker.getClass());
399         if (upd != null)
400         {
401           upd.remove(worker);
402         }
403       }
404     }
405   }
406 }