JAL-1645 source formatting and organise imports
[jalview.git] / src / jalview / ws / jws2 / AbstractJabaCalcWorker.java
1 package jalview.ws.jws2;
2
3 import jalview.analysis.AlignSeq;
4 import jalview.analysis.SeqsetUtils;
5 import jalview.api.AlignViewportI;
6 import jalview.api.AlignmentViewPanel;
7 import jalview.datamodel.AlignmentAnnotation;
8 import jalview.datamodel.AlignmentI;
9 import jalview.datamodel.AnnotatedCollectionI;
10 import jalview.datamodel.SequenceI;
11 import jalview.gui.AlignFrame;
12 import jalview.gui.IProgressIndicator;
13 import jalview.workers.AlignCalcWorker;
14 import jalview.ws.jws2.dm.AAConSettings;
15 import jalview.ws.jws2.dm.JabaWsParamSet;
16 import jalview.ws.jws2.jabaws2.Jws2Instance;
17 import jalview.ws.params.WsParamSetI;
18
19 import java.util.ArrayList;
20 import java.util.HashMap;
21 import java.util.List;
22 import java.util.Map;
23
24 import compbio.data.sequence.FastaSequence;
25 import compbio.metadata.Argument;
26 import compbio.metadata.ChunkHolder;
27 import compbio.metadata.JobStatus;
28 import compbio.metadata.JobSubmissionException;
29 import compbio.metadata.Option;
30 import compbio.metadata.ResultNotAvailableException;
31
32 public abstract class AbstractJabaCalcWorker extends AlignCalcWorker
33 {
34
35   protected Jws2Instance service;
36
37   protected WsParamSetI preset;
38
39   protected List<Argument> arguments;
40
41   protected IProgressIndicator guiProgress;
42
43   protected boolean submitGaps = true;
44
45   /**
46    * Recover any existing parameters for this service
47    */
48   protected void initViewportParams()
49   {
50     if (getCalcId() != null)
51     {
52       ((jalview.gui.AlignViewport) alignViewport).setCalcIdSettingsFor(
53               getCalcId(),
54               new AAConSettings(true, service, this.preset,
55                       (arguments != null) ? JabaParamStore
56                               .getJwsArgsfromJaba(arguments) : null), true);
57     }
58   }
59
60   /**
61    * 
62    * @return null or a string used to recover all annotation generated by this
63    *         worker
64    */
65   public abstract String getCalcId();
66
67   public WsParamSetI getPreset()
68   {
69     return preset;
70   }
71
72   public List<Argument> getArguments()
73   {
74     return arguments;
75   }
76
77   /**
78    * reconfigure and restart the AAConClient. This method will spawn a new
79    * thread that will wait until any current jobs are finished, modify the
80    * parameters and restart the conservation calculation with the new values.
81    * 
82    * @param newpreset
83    * @param newarguments
84    */
85   public void updateParameters(final WsParamSetI newpreset,
86           final List<Argument> newarguments)
87   {
88     preset = newpreset;
89     arguments = newarguments;
90     calcMan.startWorker(this);
91     initViewportParams();
92   }
93
94   public List<Option> getJabaArguments()
95   {
96     List<Option> newargs = new ArrayList<Option>();
97     if (preset != null && preset instanceof JabaWsParamSet)
98     {
99       newargs.addAll(((JabaWsParamSet) preset).getjabaArguments());
100     }
101     if (arguments != null && arguments.size() > 0)
102     {
103       for (Argument rg : arguments)
104       {
105         if (Option.class.isAssignableFrom(rg.getClass()))
106         {
107           newargs.add((Option) rg);
108         }
109       }
110     }
111     return newargs;
112   }
113
114   protected boolean alignedSeqs = true;
115
116   protected boolean nucleotidesAllowed = false;
117
118   protected boolean proteinAllowed = false;
119
120   /**
121    * record sequences for mapping result back to afterwards
122    */
123   protected boolean bySequence = false;
124
125   protected Map<String, SequenceI> seqNames;
126
127   protected boolean[] gapMap;
128
129   int realw;
130
131   protected int start;
132
133   int end;
134
135   public AbstractJabaCalcWorker(AlignViewportI alignViewport,
136           AlignmentViewPanel alignPanel)
137   {
138     super(alignViewport, alignPanel);
139   }
140
141   public AbstractJabaCalcWorker(Jws2Instance service,
142           AlignFrame alignFrame, WsParamSetI preset, List<Argument> paramset)
143   {
144     this(alignFrame.getCurrentView(), alignFrame.alignPanel);
145     this.guiProgress = alignFrame;
146     this.preset = preset;
147     this.arguments = paramset;
148     this.service = service;
149   }
150
151   /**
152    * 
153    * @return true if the submission thread should attempt to submit data
154    */
155   abstract boolean hasService();
156
157   volatile String rslt = "JOB NOT DEFINED";
158
159   @Override
160   public void run()
161   {
162     if (!hasService())
163     {
164       return;
165     }
166     long progressId = -1;
167
168     int serverErrorsLeft = 3;
169
170     StringBuffer msg = new StringBuffer();
171     try
172     {
173       if (checkDone())
174       {
175         return;
176       }
177       List<compbio.data.sequence.FastaSequence> seqs = getInputSequences(
178               alignViewport.getAlignment(),
179               bySequence ? alignViewport.getSelectionGroup() : null);
180
181       if (seqs == null || !checkValidInputSeqs(true, seqs))
182       {
183         calcMan.workerComplete(this);
184         return;
185       }
186
187       AlignmentAnnotation[] aa = alignViewport.getAlignment()
188               .getAlignmentAnnotation();
189       if (guiProgress != null)
190       {
191         guiProgress.setProgressBar("JABA " + getServiceActionText(),
192                 progressId = System.currentTimeMillis());
193       }
194       rslt = submitToService(seqs);
195
196       boolean finished = false;
197       long rpos = 0;
198       do
199       {
200         JobStatus status = getJobStatus(rslt);
201         if (status.equals(JobStatus.FINISHED))
202         {
203           finished = true;
204         }
205         if (calcMan.isPending(this) && isInteractiveUpdate())
206         {
207           finished = true;
208           // cancel this job and yield to the new job
209           try
210           {
211             if (cancelJob(rslt))
212             {
213               System.err.println("Cancelled AACon job: " + rslt);
214             }
215             else
216             {
217               System.err.println("FAILED TO CANCEL AACon job: " + rslt);
218             }
219
220           } catch (Exception x)
221           {
222
223           }
224           rslt = "CANCELLED JOB";
225           return;
226         }
227         long cpos;
228         ChunkHolder stats = null;
229         do
230         {
231           cpos = rpos;
232           boolean retry = false;
233           do
234           {
235             try
236             {
237               stats = pullExecStatistics(rslt, rpos);
238             } catch (Exception x)
239             {
240
241               if (x.getMessage().contains(
242                       "Position in a file could not be negative!"))
243               {
244                 // squash index out of bounds exception- seems to happen for
245                 // disorder predictors which don't (apparently) produce any
246                 // progress information and JABA server throws an exception
247                 // because progress length is -1.
248                 stats = null;
249               }
250               else
251               {
252                 if (--serverErrorsLeft > 0)
253                 {
254                   retry = true;
255                   try
256                   {
257                     Thread.sleep(200);
258                   } catch (InterruptedException q)
259                   {
260                   }
261                   ;
262                 }
263                 else
264                 {
265                   throw x;
266                 }
267               }
268             }
269           } while (retry);
270           if (stats != null)
271           {
272             System.out.print(stats.getChunk());
273             msg.append(stats);
274             rpos = stats.getNextPosition();
275           }
276         } while (stats != null && rpos > cpos);
277
278         if (!finished && status.equals(JobStatus.FAILED))
279         {
280           try
281           {
282             Thread.sleep(200);
283           } catch (InterruptedException x)
284           {
285           }
286           ;
287         }
288       } while (!finished);
289       if (serverErrorsLeft > 0)
290       {
291         try
292         {
293           Thread.sleep(200);
294         } catch (InterruptedException x)
295         {
296         }
297         if (collectAnnotationResultsFor(rslt))
298         {
299           jalview.bin.Cache.log
300                   .debug("Updating result annotation from Job " + rslt
301                           + " at " + service.getUri());
302           updateResultAnnotation(true);
303           ap.adjustAnnotationHeight();
304         }
305       }
306     }
307
308     catch (JobSubmissionException x)
309     {
310
311       System.err.println("submission error with " + getServiceActionText()
312               + " :");
313       x.printStackTrace();
314       calcMan.workerCannotRun(this);
315     } catch (ResultNotAvailableException x)
316     {
317       System.err.println("collection error:\nJob ID: " + rslt);
318       x.printStackTrace();
319       calcMan.workerCannotRun(this);
320
321     } catch (OutOfMemoryError error)
322     {
323       calcMan.workerCannotRun(this);
324
325       // consensus = null;
326       // hconsensus = null;
327       ap.raiseOOMWarning(getServiceActionText(), error);
328     } catch (Exception x)
329     {
330       calcMan.workerCannotRun(this);
331
332       // consensus = null;
333       // hconsensus = null;
334       System.err
335               .println("Blacklisting worker due to unexpected exception:");
336       x.printStackTrace();
337     } finally
338     {
339
340       calcMan.workerComplete(this);
341       if (ap != null)
342       {
343         calcMan.workerComplete(this);
344         if (guiProgress != null && progressId != -1)
345         {
346           guiProgress.setProgressBar("", progressId);
347         }
348         ap.paintAlignment(true);
349       }
350       if (msg.length() > 0)
351       {
352         // TODO: stash message somewhere in annotation or alignment view.
353         // code below shows result in a text box popup
354         /*
355          * jalview.gui.CutAndPasteTransfer cap = new
356          * jalview.gui.CutAndPasteTransfer(); cap.setText(msg.toString());
357          * jalview.gui.Desktop.addInternalFrame(cap,
358          * "Job Status for "+getServiceActionText(), 600, 400);
359          */
360       }
361     }
362
363   }
364
365   /**
366    * validate input for dynamic/non-dynamic update context
367    * 
368    * @param dynamic
369    * @param seqs
370    * @return true if input is valid
371    */
372   abstract boolean checkValidInputSeqs(boolean dynamic,
373           List<FastaSequence> seqs);
374
375   abstract String submitToService(
376           List<compbio.data.sequence.FastaSequence> seqs)
377           throws JobSubmissionException;
378
379   abstract boolean cancelJob(String rslt) throws Exception;
380
381   abstract JobStatus getJobStatus(String rslt) throws Exception;
382
383   abstract ChunkHolder pullExecStatistics(String rslt, long rpos);
384
385   abstract boolean collectAnnotationResultsFor(String rslt)
386           throws ResultNotAvailableException;
387
388   public void cancelCurrentJob()
389   {
390     try
391     {
392       String id = rslt;
393       if (cancelJob(rslt))
394       {
395         System.err.println("Cancelled job " + id);
396       }
397       else
398       {
399         System.err.println("Job " + id + " couldn't be cancelled.");
400       }
401     } catch (Exception q)
402     {
403       q.printStackTrace();
404     }
405   }
406
407   /**
408    * Interactive updating. Analysis calculations that work on the currently
409    * displayed alignment data should cancel existing jobs when the input data
410    * has changed.
411    * 
412    * @return true if a running job should be cancelled because new input data is
413    *         available for analysis
414    */
415   abstract boolean isInteractiveUpdate();
416
417   public List<FastaSequence> getInputSequences(AlignmentI alignment,
418           AnnotatedCollectionI inputSeqs)
419   {
420     if (alignment == null || alignment.getWidth() <= 0
421             || alignment.getSequences() == null || alignment.isNucleotide() ? !nucleotidesAllowed
422             : !proteinAllowed)
423     {
424       return null;
425     }
426     if (inputSeqs == null || inputSeqs.getWidth() <= 0
427             || inputSeqs.getSequences() == null
428             || inputSeqs.getSequences().size() < 1)
429     {
430       inputSeqs = alignment;
431     }
432
433     List<compbio.data.sequence.FastaSequence> seqs = new ArrayList<compbio.data.sequence.FastaSequence>();
434
435     int minlen = 10;
436     int ln = -1;
437     if (bySequence)
438     {
439       seqNames = new HashMap<String, SequenceI>();
440     }
441     gapMap = new boolean[0];
442     start = inputSeqs.getStartRes();
443     end = inputSeqs.getEndRes();
444
445     for (SequenceI sq : ((List<SequenceI>) inputSeqs.getSequences()))
446     {
447       if (bySequence ? sq.findPosition(end + 1)
448               - sq.findPosition(start + 1) > minlen - 1 : sq.getEnd()
449               - sq.getStart() > minlen - 1)
450       {
451         String newname = SeqsetUtils.unique_name(seqs.size() + 1);
452         // make new input sequence with or without gaps
453         if (seqNames != null)
454         {
455           seqNames.put(newname, sq);
456         }
457         FastaSequence seq;
458         if (submitGaps)
459         {
460           seqs.add(seq = new compbio.data.sequence.FastaSequence(newname,
461                   sq.getSequenceAsString()));
462           if (gapMap == null || gapMap.length < seq.getSequence().length())
463           {
464             boolean[] tg = gapMap;
465             gapMap = new boolean[seq.getLength()];
466             System.arraycopy(tg, 0, gapMap, 0, tg.length);
467             for (int p = tg.length; p < gapMap.length; p++)
468             {
469               gapMap[p] = false; // init as a gap
470             }
471           }
472           for (int apos : sq.gapMap())
473           {
474             gapMap[apos] = true; // aligned.
475           }
476         }
477         else
478         {
479           seqs.add(seq = new compbio.data.sequence.FastaSequence(newname,
480                   AlignSeq.extractGaps(jalview.util.Comparison.GapChars,
481                           sq.getSequenceAsString(start, end + 1))));
482         }
483         if (seq.getSequence().length() > ln)
484         {
485           ln = seq.getSequence().length();
486         }
487       }
488     }
489     if (alignedSeqs && submitGaps)
490     {
491       realw = 0;
492       for (int i = 0; i < gapMap.length; i++)
493       {
494         if (gapMap[i])
495         {
496           realw++;
497         }
498       }
499       // try real hard to return something submittable
500       // TODO: some of AAcon measures need a minimum of two or three amino
501       // acids at each position, and AAcon doesn't gracefully degrade.
502       for (int p = 0; p < seqs.size(); p++)
503       {
504         FastaSequence sq = seqs.get(p);
505         int l = sq.getSequence().length();
506         // strip gapped columns
507         char[] padded = new char[realw], orig = sq.getSequence()
508                 .toCharArray();
509         for (int i = 0, pp = 0; i < realw; pp++)
510         {
511           if (gapMap[pp])
512           {
513             if (orig.length > pp)
514             {
515               padded[i++] = orig[pp];
516             }
517             else
518             {
519               padded[i++] = '-';
520             }
521           }
522         }
523         seqs.set(p, new compbio.data.sequence.FastaSequence(sq.getId(),
524                 new String(padded)));
525       }
526     }
527     return seqs;
528   }
529
530   @Override
531   public void updateAnnotation()
532   {
533     updateResultAnnotation(false);
534   }
535
536   public abstract void updateResultAnnotation(boolean immediate);
537
538   public abstract String getServiceActionText();
539
540   /**
541    * notify manager that we have started, and wait for a free calculation slot
542    * 
543    * @return true if slot is obtained and work still valid, false if another
544    *         thread has done our work for us.
545    */
546   protected boolean checkDone()
547   {
548     calcMan.notifyStart(this);
549     ap.paintAlignment(false);
550     while (!calcMan.notifyWorking(this))
551     {
552       if (calcMan.isWorking(this))
553       {
554         return true;
555       }
556       try
557       {
558         if (ap != null)
559         {
560           ap.paintAlignment(false);
561         }
562
563         Thread.sleep(200);
564       } catch (Exception ex)
565       {
566         ex.printStackTrace();
567       }
568     }
569     if (alignViewport.isClosed())
570     {
571       abortAndDestroy();
572       return true;
573     }
574     return false;
575   }
576
577   protected void updateOurAnnots(List<AlignmentAnnotation> ourAnnot)
578   {
579     List<AlignmentAnnotation> our = ourAnnots;
580     ourAnnots = ourAnnot;
581     AlignmentI alignment = alignViewport.getAlignment();
582     if (our != null)
583     {
584       if (our.size() > 0)
585       {
586         for (AlignmentAnnotation an : our)
587         {
588           if (!ourAnnots.contains(an))
589           {
590             // remove the old annotation
591             alignment.deleteAnnotation(an);
592           }
593         }
594       }
595       our.clear();
596
597       ap.adjustAnnotationHeight();
598     }
599   }
600
601 }