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