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