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