JAL-2068 deleting user-defined annotation removes the worker
[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   private volatile List<AlignCalcWorkerI> restartable = Collections
38           .synchronizedList(new ArrayList<AlignCalcWorkerI>());
39
40   private volatile List<AlignCalcWorkerI> blackList = Collections
41           .synchronizedList(new ArrayList<AlignCalcWorkerI>());
42
43   /**
44    * global record of calculations in progress
45    */
46   private volatile Map<Class, AlignCalcWorkerI> inProgress = Collections
47           .synchronizedMap(new Hashtable<Class, AlignCalcWorkerI>());
48
49   /**
50    * record of calculations pending or in progress in the current context
51    */
52   private volatile Map<Class, List<AlignCalcWorkerI>> updating = Collections
53           .synchronizedMap(new Hashtable<Class, List<AlignCalcWorkerI>>());
54
55   @Override
56   public void notifyStart(AlignCalcWorkerI worker)
57   {
58     synchronized (updating)
59     {
60       List<AlignCalcWorkerI> upd = updating.get(worker.getClass());
61       if (upd == null)
62       {
63         updating.put(
64                 worker.getClass(),
65                 upd = Collections
66                         .synchronizedList(new ArrayList<AlignCalcWorkerI>()));
67       }
68       synchronized (upd)
69       {
70         upd.add(worker);
71       }
72     }
73   }
74
75   @Override
76   public boolean alreadyDoing(AlignCalcWorkerI worker)
77   {
78     synchronized (inProgress)
79     {
80       return inProgress.containsKey(worker.getClass());
81     }
82   }
83
84   /*
85    * (non-Javadoc)
86    * 
87    * @see jalview.api.AlignCalcManagerI#isPending(jalview.api.AlignCalcWorkerI)
88    */
89   @Override
90   public boolean isPending(AlignCalcWorkerI workingClass)
91   {
92     List<AlignCalcWorkerI> upd;
93     synchronized (updating)
94     {
95       upd = updating.get(workingClass.getClass());
96       if (upd == null)
97       {
98         return false;
99       }
100       synchronized (upd)
101       {
102         if (upd.size() > 1)
103         {
104           return true;
105         }
106       }
107       return false;
108     }
109   }
110
111   // TODO make into api method if needed ?
112   public int numberLive(AlignCalcWorkerI worker)
113   {
114     synchronized (updating)
115     {
116       List<AlignCalcWorkerI> upd = updating.get(worker.getClass());
117       if (upd == null)
118       {
119         return 0;
120       }
121       ;
122       return upd.size();
123     }
124   }
125
126   @Override
127   public boolean notifyWorking(AlignCalcWorkerI worker)
128   {
129     synchronized (inProgress)
130     {
131       // TODO: decide if we should throw exceptions here if multiple workers
132       // start to work
133       if (inProgress.get(worker.getClass()) != null)
134       {
135         if (false)
136         {
137           System.err
138                   .println("Warning: Multiple workers are running of type "
139                           + worker.getClass());
140         }
141         return false;
142       }
143       inProgress.put(worker.getClass(), worker);
144     }
145     return true;
146   }
147
148   private final HashSet<AlignCalcWorkerI> canUpdate = new HashSet<AlignCalcWorkerI>();
149
150   @Override
151   public void workerComplete(AlignCalcWorkerI worker)
152   {
153     synchronized (inProgress)
154     {
155       // System.err.println("Worker "+worker.getClass()+" marked as complete.");
156       inProgress.remove(worker.getClass());
157       List<AlignCalcWorkerI> upd = updating.get(worker.getClass());
158       if (upd != null)
159       {
160         synchronized (upd)
161         {
162           upd.remove(worker);
163         }
164         canUpdate.add(worker);
165       }
166     }
167   }
168
169   @Override
170   public void disableWorker(AlignCalcWorkerI worker)
171   {
172     synchronized (blackList)
173     {
174       blackList.add(worker);
175     }
176   }
177
178   public boolean isBlackListed(AlignCalcWorkerI worker)
179   {
180     synchronized (blackList)
181     {
182       return blackList.contains(worker);
183     }
184   }
185
186   @Override
187   public void startWorker(AlignCalcWorkerI worker)
188   {
189     if (!isBlackListed(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.get(worker.getClass()) == 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(AlignmentAnnotation alignmentAnnotation)
244   {
245     synchronized (inProgress)
246     {
247       for (AlignCalcWorkerI worker : inProgress.values())
248       {
249         if (worker.involves(alignmentAnnotation))
250         {
251           return true;
252         }
253       }
254     }
255     synchronized (updating)
256     {
257       for (List<AlignCalcWorkerI> workers : updating.values())
258       {
259         for (AlignCalcWorkerI worker : workers)
260         {
261           if (worker.involves(alignmentAnnotation))
262           {
263             return true;
264           }
265         }
266       }
267     }
268     return false;
269   }
270
271   @Override
272   public void updateAnnotationFor(Class workerClass)
273   {
274
275     AlignCalcWorkerI[] workers;
276     synchronized (canUpdate)
277     {
278       workers = canUpdate.toArray(new AlignCalcWorkerI[0]);
279     }
280     for (AlignCalcWorkerI worker : workers)
281     {
282       if (workerClass.equals(worker.getClass()))
283       {
284         worker.updateAnnotation();
285       }
286     }
287   }
288
289   @Override
290   public List<AlignCalcWorkerI> getRegisteredWorkersOfClass(
291           Class workerClass)
292   {
293     List<AlignCalcWorkerI> workingClass = new ArrayList<AlignCalcWorkerI>();
294     AlignCalcWorkerI[] workers;
295     synchronized (canUpdate)
296     {
297       workers = canUpdate.toArray(new AlignCalcWorkerI[canUpdate.size()]);
298     }
299     for (AlignCalcWorkerI worker : workers)
300     {
301       if (workerClass.equals(worker.getClass()))
302       {
303         workingClass.add(worker);
304       }
305     }
306     return (workingClass.size() == 0) ? null : workingClass;
307   }
308
309   @Override
310   public boolean startRegisteredWorkersOfClass(Class workerClass)
311   {
312     List<AlignCalcWorkerI> workers = getRegisteredWorkersOfClass(workerClass);
313     if (workers == null)
314     {
315       return false;
316     }
317     for (AlignCalcWorkerI worker : workers)
318     {
319       if (!isPending(worker))
320       {
321         startWorker(worker);
322       }
323       else
324       {
325         System.err.println("Pending exists for " + workerClass);
326       }
327     }
328     return true;
329   }
330
331   @Override
332   public void enableWorker(AlignCalcWorkerI worker)
333   {
334     synchronized (blackList)
335     {
336       blackList.remove(worker);
337     }
338   }
339
340   @Override
341   public void removeRegisteredWorkersOfClass(Class typeToRemove)
342   {
343     List<AlignCalcWorkerI> workers = getRegisteredWorkersOfClass(typeToRemove);
344     List<AlignCalcWorkerI> removable = new ArrayList<AlignCalcWorkerI>();
345     Set<AlignCalcWorkerI> toremovannot = new HashSet<AlignCalcWorkerI>();
346     synchronized (restartable)
347     {
348       for (AlignCalcWorkerI worker : restartable)
349       {
350         if (typeToRemove.equals(worker.getClass()))
351         {
352           removable.add(worker);
353           toremovannot.add(worker);
354         }
355       }
356       restartable.removeAll(removable);
357     }
358     synchronized (canUpdate)
359     {
360       for (AlignCalcWorkerI worker : canUpdate)
361       {
362         if (typeToRemove.equals(worker.getClass()))
363         {
364           removable.add(worker);
365           toremovannot.add(worker);
366         }
367       }
368       canUpdate.removeAll(removable);
369     }
370     // TODO: finish testing this extension
371
372     /*
373      * synchronized (inProgress) { // need to kill or mark as dead any running
374      * threads... (inProgress.get(typeToRemove)); }
375      * 
376      * if (workers == null) { return; } for (AlignCalcWorkerI worker : workers)
377      * {
378      * 
379      * if (isPending(worker)) { worker.abortAndDestroy(); startWorker(worker); }
380      * else { System.err.println("Pending exists for " + workerClass); } }
381      */
382   }
383
384   /**
385    * Deletes the worker that update the given annotation, provided it is marked
386    * as deletable.
387    */
388   @Override
389   public void removeWorkerForAnnotation(AlignmentAnnotation ann)
390   {
391     /*
392      * first just find those to remove (to avoid
393      * ConcurrentModificationException)
394      */
395     List<AlignCalcWorkerI> toRemove = new ArrayList<AlignCalcWorkerI>();
396     for (AlignCalcWorkerI worker : restartable)
397     {
398       if (worker.involves(ann))
399       {
400         if (worker.isDeletable())
401         {
402           toRemove.add(worker);
403         }
404       }
405     }
406
407     /*
408      * remove all references to the workers
409      */
410     for (AlignCalcWorkerI worker : toRemove)
411     {
412       restartable.remove(worker);
413       blackList.remove(worker);
414     }
415   }
416 }