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