1 package jalview.workers;
3 import java.util.HashMap;
6 import java.util.NoSuchElementException;
7 import java.util.Objects;
8 import java.util.WeakHashMap;
9 import java.util.concurrent.CopyOnWriteArrayList;
10 import java.util.concurrent.Executors;
11 import java.util.concurrent.Future;
12 import java.util.concurrent.ScheduledExecutorService;
13 import java.util.concurrent.TimeUnit;
15 import static java.lang.String.format;
16 import static java.util.Collections.synchronizedMap;
17 import static java.util.Collections.unmodifiableList;
19 import java.util.ArrayList;
21 import jalview.api.AlignCalcListener;
22 import jalview.api.AlignCalcManagerI2;
23 import jalview.api.AlignCalcWorkerI;
24 import jalview.api.PollableAlignCalcWorkerI;
25 import jalview.bin.Cache;
26 import jalview.bin.Console;
27 import jalview.datamodel.AlignmentAnnotation;
29 public class AlignCalcManager2 implements AlignCalcManagerI2
31 private abstract class WorkerManager
33 protected volatile boolean enabled = true;
35 protected AlignCalcWorkerI worker;
37 WorkerManager(AlignCalcWorkerI worker)
42 protected AlignCalcWorkerI getWorker()
52 void setEnabled(boolean enabled)
54 this.enabled = enabled;
57 synchronized void restart()
74 protected boolean isRegistered()
76 return registered.containsKey(getWorker());
79 abstract boolean isWorking();
81 protected abstract void submit();
83 abstract void cancel();
86 private class SimpleWorkerManager extends WorkerManager
88 private Future<?> task = null;
90 SimpleWorkerManager(AlignCalcWorkerI worker)
98 return task != null && !task.isDone();
102 protected void submit()
104 if (task != null && !(task.isDone() || task.isCancelled()))
106 throw new IllegalStateException(
107 "Cannot submit new task if the prevoius one is still running");
110 format("Worker %s queued", getWorker().getClass().getName()));
111 task = executor.submit(() -> {
114 Console.debug(format("Worker %s started",
115 getWorker().getClass().getName()));
117 Console.debug(format("Worker %s finished",
118 getWorker().getClass().getName()));
119 } catch (InterruptedException e)
121 Console.debug(format("Worker %s interrupted",
122 getWorker().getClass().getName()));
123 } catch (Throwable th)
125 Console.debug(format("Worker %s failed",
126 getWorker().getClass().getName()), th);
131 // delete worker reference so garbage collector can remove it
139 synchronized void cancel()
145 Console.debug(format("Cancelling worker %s",
146 getWorker().getClass().getName()));
151 private class PollableWorkerManager extends WorkerManager
153 private Future<?> task = null;
155 PollableWorkerManager(PollableAlignCalcWorkerI worker)
161 protected PollableAlignCalcWorkerI getWorker()
163 return (PollableAlignCalcWorkerI) super.getWorker();
169 return task != null && !task.isDone();
172 protected void submit()
174 if (task != null && !(task.isDone() || task.isCancelled()))
176 throw new IllegalStateException(
177 "Cannot submit new task if the prevoius one is still running");
180 format("Worker %s queued", getWorker().getClass().getName()));
181 final var runnable = new Runnable()
183 private boolean started = false;
185 private boolean completed = false;
187 Future<?> future = null;
196 Console.debug(format("Worker %s started",
197 getWorker().getClass().getName()));
198 getWorker().startUp();
203 Console.debug(format("Polling worker %s",
204 getWorker().getClass().getName()));
205 if (getWorker().poll())
207 Console.debug(format("Worker %s finished",
208 getWorker().getClass().getName()));
212 } catch (Throwable th)
214 Console.debug(format("Worker %s failed",
215 getWorker().getClass().getName()), th);
220 final var worker = getWorker();
222 PollableWorkerManager.super.worker = null;
223 Console.debug(format("Finalizing completed worker %s",
224 worker.getClass().getName()));
226 // almost impossible, but the future may be null at this point
227 // let it throw NPE to cancel forcefully
228 future.cancel(false);
232 runnable.future = task = executor.scheduleWithFixedDelay(runnable, 10,
233 1000, TimeUnit.MILLISECONDS);
236 synchronized protected void cancel()
242 Console.debug(format("Cancelling worker %s",
243 getWorker().getClass().getName()));
245 executor.submit(() -> {
246 final var worker = getWorker();
248 PollableWorkerManager.super.worker = null;
252 Console.debug(format("Finalizing cancelled worker %s",
253 worker.getClass().getName()));
260 private final ScheduledExecutorService executor = Executors
261 .newSingleThreadScheduledExecutor();
263 private final Map<AlignCalcWorkerI, WorkerManager> registered = synchronizedMap(
266 private final Map<AlignCalcWorkerI, WorkerManager> oneshot = synchronizedMap(
267 new WeakHashMap<>());
269 private final List<AlignCalcListener> listeners = new CopyOnWriteArrayList<>();
271 private WorkerManager createManager(AlignCalcWorkerI worker)
273 if (worker instanceof PollableAlignCalcWorkerI)
275 return new PollableWorkerManager((PollableAlignCalcWorkerI) worker);
279 return new SimpleWorkerManager(worker);
284 public void registerWorker(AlignCalcWorkerI worker)
286 Objects.requireNonNull(worker);
287 WorkerManager manager = createManager(worker);
288 registered.putIfAbsent(worker, manager);
293 public List<AlignCalcWorkerI> getWorkers()
295 List<AlignCalcWorkerI> result = new ArrayList<>(registered.size());
296 result.addAll(registered.keySet());
301 public List<AlignCalcWorkerI> getWorkersOfClass(
302 Class<? extends AlignCalcWorkerI> cls)
304 List<AlignCalcWorkerI> collected = new ArrayList<>();
305 for (var worker : getWorkers())
307 if (worker.getClass().equals(cls))
309 collected.add(worker);
312 return unmodifiableList(collected);
316 public List<AlignCalcWorkerI> getWorkersForName(String name)
318 List<AlignCalcWorkerI> collected = new ArrayList<>();
319 for (var worker : getWorkers())
321 if (worker.getCalcName().equals(name))
323 collected.add(worker);
330 public void removeWorker(AlignCalcWorkerI worker)
332 if (worker.isDeletable())
334 registered.remove(worker);
339 public void removeWorkerForAnnotation(AlignmentAnnotation annot)
341 synchronized (registered)
343 for (var worker : getWorkers())
345 if (worker.involves(annot))
347 removeWorker(worker);
354 public void removeWorkersOfClass(Class<? extends AlignCalcWorkerI> cls)
356 synchronized (registered)
358 for (var worker : getWorkers())
360 if (worker.getClass().equals(cls))
362 removeWorker(worker);
369 public void removeWorkersForName(String name)
371 synchronized (registered)
373 for (var worker : getWorkers())
375 if (worker.getCalcName().equals(name))
377 removeWorker(worker);
384 public void disableWorker(AlignCalcWorkerI worker)
386 // Null pointer check might be needed
387 registered.get(worker).setEnabled(false);
391 public void enableWorker(AlignCalcWorkerI worker)
393 // Null pointer check might be needed
394 registered.get(worker).setEnabled(true);
398 public boolean isDisabled(AlignCalcWorkerI worker)
400 if (registered.containsKey(worker))
402 return !registered.get(worker).isEnabled();
411 public boolean isWorking(AlignCalcWorkerI worker)
413 var manager = registered.get(worker);
415 manager = oneshot.get(worker);
419 return manager.isWorking();
423 public boolean isWorkingWithAnnotation(AlignmentAnnotation annot)
425 synchronized (registered)
427 for (var entry : registered.entrySet())
428 if (entry.getKey().involves(annot) && entry.getValue().isWorking())
431 synchronized (oneshot)
433 for (var entry : registered.entrySet())
434 if (entry.getKey().involves(annot) && entry.getValue().isWorking())
441 public boolean isWorking()
443 synchronized (registered)
445 for (var manager : registered.values())
446 if (manager.isWorking())
449 synchronized (oneshot)
451 for (var manager : oneshot.values())
452 if (manager.isWorking())
459 public void startWorker(AlignCalcWorkerI worker)
461 Objects.requireNonNull(worker);
462 var manager = registered.get(worker);
465 Console.warn("Starting unregistered worker " + worker);
466 manager = createManager(worker);
467 oneshot.put(worker, manager);
473 public void restartWorkers()
475 synchronized (registered)
477 for (var manager : registered.values())
485 public void cancelWorker(AlignCalcWorkerI worker)
487 Objects.requireNonNull(worker);
488 var manager = registered.get(worker);
490 manager = oneshot.get(worker);
493 throw new NoSuchElementException();
498 private void notifyQueued(AlignCalcWorkerI worker)
500 for (AlignCalcListener listener : listeners)
502 listener.workerQueued(worker);
506 private void notifyStarted(AlignCalcWorkerI worker)
508 for (AlignCalcListener listener : listeners)
510 listener.workerStarted(worker);
514 private void notifyCompleted(AlignCalcWorkerI worker)
516 for (AlignCalcListener listener : listeners)
520 listener.workerCompleted(worker);
521 } catch (RuntimeException e)
528 private void notifyCancelled(AlignCalcWorkerI worker)
530 for (AlignCalcListener listener : listeners)
534 listener.workerCancelled(worker);
535 } catch (RuntimeException e)
542 private void notifyExceptional(AlignCalcWorkerI worker,
545 for (AlignCalcListener listener : listeners)
549 listener.workerExceptional(worker, throwable);
550 } catch (RuntimeException e)
558 public void addAlignCalcListener(AlignCalcListener listener)
560 listeners.add(listener);
564 public void removeAlignCalcListener(AlignCalcListener listener)
566 listeners.remove(listener);
570 public void shutdown()
572 executor.shutdownNow();