imports and formatting
[jalview.git] / src / jalview / ws / jws1 / JPredThread.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.jws1;
22
23 import jalview.analysis.AlignSeq;
24 import jalview.bin.Cache;
25 import jalview.datamodel.Alignment;
26 import jalview.datamodel.AlignmentAnnotation;
27 import jalview.datamodel.AlignmentView;
28 import jalview.datamodel.ColumnSelection;
29 import jalview.datamodel.SequenceI;
30 import jalview.gui.AlignFrame;
31 import jalview.gui.Desktop;
32 import jalview.gui.WebserviceInfo;
33 import jalview.io.FormatAdapter;
34 import jalview.util.Comparison;
35 import jalview.util.MessageManager;
36 import jalview.ws.AWsJob;
37 import jalview.ws.JobStateSummary;
38 import jalview.ws.WSClientI;
39
40 import java.util.Hashtable;
41 import java.util.List;
42
43 import vamsas.objects.simple.JpredResult;
44
45 class JPredThread extends JWS1Thread implements WSClientI
46 {
47   // TODO: put mapping between JPredJob input and input data here -
48   // JNetAnnotation adding is done after result parsing.
49   class JPredJob extends WSJob
50   {
51     // TODO: make JPredJob deal only with what was sent to and received from a
52     // JNet service
53     int[] predMap = null; // mapping from sequence(i) to the original
54
55     // sequence(predMap[i]) being predicted on
56
57     vamsas.objects.simple.Sequence sequence;
58
59     vamsas.objects.simple.Msfalignment msa;
60
61     java.util.Hashtable SequenceInfo = null;
62
63     int msaIndex = 0; // the position of the original sequence in the array of
64
65     // Sequences in the input object that this job holds a
66     // prediction for
67
68     /**
69      * 
70      * @return true if getResultSet will return a valid alignment and prediction
71      *         result.
72      */
73     public boolean hasResults()
74     {
75       if (subjobComplete && result != null && result.isFinished()
76               && ((JpredResult) result).getPredfile() != null
77               && ((JpredResult) result).getAligfile() != null)
78       {
79         return true;
80       }
81       return false;
82     }
83
84     public boolean hasValidInput()
85     {
86       if (sequence != null)
87       {
88         return true;
89       }
90       return false;
91     }
92
93     /**
94      * 
95      * @return null or Object[] { annotated alignment for this prediction,
96      *         ColumnSelection for this prediction} or null if no results
97      *         available.
98      * @throws Exception
99      */
100     public Object[] getResultSet() throws Exception
101     {
102       if (result == null || !result.isFinished())
103       {
104         return null;
105       }
106       Alignment al = null;
107       ColumnSelection alcsel = null;
108       int FirstSeq = -1; // the position of the query sequence in Alignment al
109
110       JpredResult result = (JpredResult) this.result;
111
112       jalview.bin.Cache.log.debug("Parsing output from JNet job.");
113       // JPredFile prediction = new JPredFile("C:/JalviewX/files/jpred.txt",
114       // "File");
115       jalview.io.JPredFile prediction = new jalview.io.JPredFile(
116               result.getPredfile(), "Paste");
117       SequenceI[] preds = prediction.getSeqsAsArray();
118       jalview.bin.Cache.log.debug("Got prediction profile.");
119
120       if ((this.msa != null) && (result.getAligfile() != null))
121       {
122         jalview.bin.Cache.log.debug("Getting associated alignment.");
123         // we ignore the returned alignment if we only predicted on a single
124         // sequence
125         String format = new jalview.io.IdentifyFile().Identify(
126                 result.getAligfile(), "Paste");
127
128         if (jalview.io.FormatAdapter.isValidFormat(format))
129         {
130           SequenceI sqs[];
131           if (predMap != null)
132           {
133             Object[] alandcolsel = input
134                     .getAlignmentAndColumnSelection(getGapChar());
135             sqs = (SequenceI[]) alandcolsel[0];
136             al = new Alignment(sqs);
137             alcsel = (ColumnSelection) alandcolsel[1];
138           }
139           else
140           {
141             al = new FormatAdapter().readFile(result.getAligfile(),
142                     "Paste", format);
143             sqs = new SequenceI[al.getHeight()];
144
145             for (int i = 0, j = al.getHeight(); i < j; i++)
146             {
147               sqs[i] = al.getSequenceAt(i);
148             }
149             if (!jalview.analysis.SeqsetUtils.deuniquify(
150                     SequenceInfo, sqs))
151             {
152               throw (new Exception(MessageManager.getString("exception.couldnt_recover_sequence_properties_for_alignment")));
153             }
154           }
155           FirstSeq = 0;
156           if (currentView.getDataset() != null)
157           {
158             al.setDataset(currentView.getDataset());
159
160           }
161           else
162           {
163             al.setDataset(null);
164           }
165           jalview.io.JnetAnnotationMaker.add_annotation(prediction, al,
166                   FirstSeq, false, predMap);
167
168         }
169         else
170         {
171           throw (new Exception(MessageManager.formatMessage("exception.unknown_format_for_file", new String[]{format,result.getAligfile()})));
172         }
173       }
174       else
175       {
176         al = new Alignment(preds);
177         FirstSeq = prediction.getQuerySeqPosition();
178         if (predMap != null)
179         {
180           char gc = getGapChar();
181           SequenceI[] sqs = (SequenceI[]) input
182                   .getAlignmentAndColumnSelection(gc)[0];
183           if (this.msaIndex >= sqs.length)
184           {
185             throw new Error(MessageManager.getString("error.implementation_error_invalid_msa_index_for_job"));
186           }
187
188           // ///
189           // Uses RemoveGapsCommand
190           // ///
191           new jalview.commands.RemoveGapsCommand(MessageManager.getString("label.remove_gaps"),
192                   new SequenceI[]
193                   { sqs[msaIndex] }, currentView);
194
195           SequenceI profileseq = al.getSequenceAt(FirstSeq);
196           profileseq.setSequence(sqs[msaIndex].getSequenceAsString());
197         }
198
199         if (!jalview.analysis.SeqsetUtils.SeqCharacterUnhash(
200                 al.getSequenceAt(FirstSeq), SequenceInfo))
201         {
202           throw (new Exception(MessageManager.getString("exception.couldnt_recover_sequence_props_for_jnet_query")));
203         }
204         else
205         {
206           if (currentView.getDataset() != null)
207           {
208             al.setDataset(currentView.getDataset());
209
210           }
211           else
212           {
213             al.setDataset(null);
214           }
215           jalview.io.JnetAnnotationMaker.add_annotation(prediction, al,
216                   FirstSeq, true, predMap);
217           SequenceI profileseq = al.getSequenceAt(0); // this includes any gaps.
218           alignToProfileSeq(al, profileseq);
219           if (predMap != null)
220           {
221             // Adjust input view for gaps
222             // propagate insertions into profile
223             alcsel = ColumnSelection.propagateInsertions(profileseq, al,
224                     input);
225           }
226         }
227       }
228       // transfer to dataset
229       for (AlignmentAnnotation alant : al.getAlignmentAnnotation())
230       {
231         if (alant.sequenceRef != null)
232         {
233           replaceAnnotationOnAlignmentWith(alant, alant.label,
234                   "jalview.jws1.Jpred", alant.sequenceRef);
235         }
236       }
237       return new Object[]
238       { al, alcsel }; // , FirstSeq, noMsa};
239     }
240
241     /**
242      * copied from JabawsCalcWorker
243      * 
244      * @param newAnnot
245      * @param typeName
246      * @param calcId
247      * @param aSeq
248      */
249     protected void replaceAnnotationOnAlignmentWith(
250             AlignmentAnnotation newAnnot, String typeName, String calcId,
251             SequenceI aSeq)
252     {
253       SequenceI dsseq = aSeq.getDatasetSequence();
254       while (dsseq.getDatasetSequence() != null)
255       {
256         dsseq = dsseq.getDatasetSequence();
257       }
258       // look for same annotation on dataset and lift this one over
259       List<AlignmentAnnotation> dsan = dsseq.getAlignmentAnnotations(
260               calcId, typeName);
261       if (dsan != null && dsan.size() > 0)
262       {
263         for (AlignmentAnnotation dssan : dsan)
264         {
265           dsseq.removeAlignmentAnnotation(dssan);
266         }
267       }
268       AlignmentAnnotation dssan = new AlignmentAnnotation(newAnnot);
269       dsseq.addAlignmentAnnotation(dssan);
270       dssan.adjustForAlignment();
271     }
272
273     /**
274      * Given an alignment where all other sequences except profileseq are
275      * aligned to the ungapped profileseq, insert gaps in the other sequences to
276      * realign them with the residues in profileseq
277      * 
278      * @param al
279      * @param profileseq
280      */
281     private void alignToProfileSeq(Alignment al, SequenceI profileseq)
282     {
283       char gc = al.getGapCharacter();
284       int[] gapMap = profileseq.gapMap();
285       // insert gaps into profile
286       for (int lp = 0, r = 0; r < gapMap.length; r++)
287       {
288         if (gapMap[r] - lp > 1)
289         {
290           StringBuffer sb = new StringBuffer();
291           for (int s = 0, ns = gapMap[r] - lp; s < ns; s++)
292           {
293             sb.append(gc);
294           }
295           for (int s = 1, ns = al.getHeight(); s < ns; s++)
296           {
297             String sq = al.getSequenceAt(s).getSequenceAsString();
298             int diff = gapMap[r] - sq.length();
299             if (diff > 0)
300             {
301               // pad gaps
302               sq = sq + sb;
303               while ((diff = gapMap[r] - sq.length()) > 0)
304               {
305                 sq = sq
306                         + ((diff >= sb.length()) ? sb.toString() : sb
307                                 .substring(0, diff));
308               }
309               al.getSequenceAt(s).setSequence(sq);
310             }
311             else
312             {
313               al.getSequenceAt(s).setSequence(
314                       sq.substring(0, gapMap[r]) + sb.toString()
315                               + sq.substring(gapMap[r]));
316             }
317           }
318         }
319         lp = gapMap[r];
320       }
321     }
322
323     public JPredJob(Hashtable SequenceInfo, SequenceI seq, int[] delMap)
324     {
325       super();
326       this.predMap = delMap;
327       String sq = AlignSeq.extractGaps(Comparison.GapChars,
328               seq.getSequenceAsString());
329       if (sq.length() >= 20)
330       {
331         this.SequenceInfo = SequenceInfo;
332         sequence = new vamsas.objects.simple.Sequence();
333         sequence.setId(seq.getName());
334         sequence.setSeq(sq);
335       }
336       else
337       {
338         errorMessage = "Sequence is too short to predict with JPred - need at least 20 amino acids.";
339       }
340     }
341
342     public JPredJob(Hashtable SequenceInfo, SequenceI[] msf, int[] delMap)
343     {
344       this(SequenceInfo, msf[0], delMap);
345       if (sequence != null)
346       {
347         if (msf.length > 1)
348         {
349           msa = new vamsas.objects.simple.Msfalignment();
350           jalview.io.PileUpfile pileup = new jalview.io.PileUpfile();
351           msa.setMsf(pileup.print(msf));
352         }
353       }
354     }
355
356     String errorMessage = "";
357
358     public String getValidationMessages()
359     {
360       return errorMessage + "\n";
361     }
362   }
363
364   ext.vamsas.Jpred server;
365
366   String altitle = "";
367
368   JPredThread(WebserviceInfo wsinfo, String altitle,
369           ext.vamsas.Jpred server, String wsurl, AlignmentView alview,
370           AlignFrame alframe)
371   {
372     super(alframe, wsinfo, alview, wsurl);
373     this.altitle = altitle;
374     this.server = server;
375   }
376
377   JPredThread(WebserviceInfo wsinfo, String altitle,
378           ext.vamsas.Jpred server, String wsurl, Hashtable SequenceInfo,
379           SequenceI seq, int[] delMap, AlignmentView alview,
380           AlignFrame alframe)
381   {
382     this(wsinfo, altitle, server, wsurl, alview, alframe);
383     JPredJob job = new JPredJob(SequenceInfo, seq, delMap);
384     if (job.hasValidInput())
385     {
386       OutputHeader = wsInfo.getProgressText();
387       jobs = new WSJob[]
388       { job };
389       job.setJobnum(0);
390     }
391     else
392     {
393       wsInfo.appendProgressText(job.getValidationMessages());
394     }
395   }
396
397   JPredThread(WebserviceInfo wsinfo, String altitle,
398           ext.vamsas.Jpred server, Hashtable SequenceInfo, SequenceI[] msf,
399           int[] delMap, AlignmentView alview, AlignFrame alframe,
400           String wsurl)
401   {
402     this(wsinfo, altitle, server, wsurl, alview, alframe);
403     JPredJob job = new JPredJob(SequenceInfo, msf, delMap);
404     if (job.hasValidInput())
405     {
406       jobs = new WSJob[]
407       { job };
408       OutputHeader = wsInfo.getProgressText();
409       job.setJobnum(0);
410     }
411     else
412     {
413       wsInfo.appendProgressText(job.getValidationMessages());
414     }
415   }
416
417   public void StartJob(AWsJob j)
418   {
419     if (!(j instanceof JPredJob))
420     {
421       throw new Error(MessageManager.formatMessage("error.implementation_error_startjob_called", new String[]{j.getClass().toString()}));
422     }
423     try
424     {
425       JPredJob job = (JPredJob) j;
426       if (job.msa != null)
427       {
428         job.setJobId(server.predictOnMsa(job.msa));
429       }
430       else if (job.sequence != null)
431       {
432         job.setJobId(server.predict(job.sequence)); // debug like : job.jobId =
433         // "/jobs/www-jpred/jp_Yatat29";//
434       }
435
436       if (job.getJobId() != null)
437       {
438         if (job.getJobId().startsWith("Broken"))
439         {
440           job.result = new JpredResult();
441           job.result.setInvalid(true);
442           job.result.setStatus(MessageManager.formatMessage("label.submission_params", new String[]{job.getJobId().toString()}));
443           throw new Exception(job.getJobId());
444         }
445         else
446         {
447           job.setSubmitted(true);
448           job.setSubjobComplete(false);
449           Cache.log.info(WsUrl + " Job Id '" + job.getJobId() + "'");
450         }
451       }
452       else
453       {
454         throw new Exception(MessageManager.getString("exception.server_timeout_try_later"));
455       }
456     } catch (Exception e)
457     {
458       // kill the whole job.
459       wsInfo.setStatus(WebserviceInfo.STATE_STOPPED_SERVERERROR);
460       if (e.getMessage().indexOf("Exception") > -1)
461       {
462         wsInfo.setStatus(j.getJobnum(),
463                 WebserviceInfo.STATE_STOPPED_SERVERERROR);
464         wsInfo.setProgressText(
465                 j.getJobnum(),
466                 "Failed to submit the prediction. (Just close the window)\n"
467                         + "It is most likely that there is a problem with the server.\n");
468         System.err
469                 .println("JPredWS Client: Failed to submit the prediction. Quite possibly because of a server error - see below)\n"
470                         + e.getMessage() + "\n");
471
472         jalview.bin.Cache.log.warn("Server Exception", e);
473       }
474       else
475       {
476         wsInfo.setStatus(j.getJobnum(), WebserviceInfo.STATE_STOPPED_ERROR);
477         // JBPNote - this could be a popup informing the user of the problem.
478         wsInfo.appendProgressText(j.getJobnum(), MessageManager.formatMessage("info.failed_to_submit_prediction", new String[]{e.getMessage(),wsInfo.getProgressText()}));
479
480         jalview.bin.Cache.log.debug(
481                 "Failed Submission of job " + j.getJobnum(), e);
482
483       }
484       j.setAllowedServerExceptions(-1);
485       j.setSubjobComplete(true);
486     }
487   }
488
489   public void parseResult()
490   {
491     int results = 0; // number of result sets received
492     JobStateSummary finalState = new JobStateSummary();
493     try
494     {
495       for (int j = 0; j < jobs.length; j++)
496       {
497         finalState.updateJobPanelState(wsInfo, OutputHeader, jobs[j]);
498         if (jobs[j].isSubmitted() && jobs[j].isSubjobComplete()
499                 && jobs[j].hasResults())
500         {
501           results++;
502         }
503       }
504     } catch (Exception ex)
505     {
506
507       Cache.log.error("Unexpected exception when processing results for "
508               + altitle, ex);
509       wsInfo.setStatus(WebserviceInfo.STATE_STOPPED_ERROR);
510     }
511     if (results > 0)
512     {
513       wsInfo.showResultsNewFrame
514               .addActionListener(new java.awt.event.ActionListener()
515               {
516                 public void actionPerformed(java.awt.event.ActionEvent evt)
517                 {
518                   displayResults(true);
519                 }
520               });
521       wsInfo.mergeResults
522               .addActionListener(new java.awt.event.ActionListener()
523               {
524                 public void actionPerformed(java.awt.event.ActionEvent evt)
525                 {
526                   displayResults(false);
527                 }
528               });
529       wsInfo.setResultsReady();
530     }
531     else
532     {
533       wsInfo.setStatus(wsInfo.STATE_STOPPED_ERROR);
534       wsInfo.appendInfoText("No jobs ran.");
535       wsInfo.setFinishedNoResults();
536     }
537   }
538
539   void displayResults(boolean newWindow)
540   {
541     // TODO: cope with multiple subjobs.
542     if (jobs != null)
543     {
544       Object[] res = null;
545       boolean msa = false;
546       for (int jn = 0; jn < jobs.length; jn++)
547       {
548         Object[] jobres = null;
549         JPredJob j = (JPredJob) jobs[jn];
550
551         if (j.hasResults())
552         {
553           // hack - we only deal with all single seuqence predictions or all
554           // profile predictions
555           msa = (j.msa != null) ? true : msa;
556           try
557           {
558             jalview.bin.Cache.log.debug("Parsing output of job " + jn);
559             jobres = j.getResultSet();
560             jalview.bin.Cache.log.debug("Finished parsing output.");
561             if (jobs.length == 1)
562             {
563               res = jobres;
564             }
565             else
566             {
567               // do merge with other job results
568               throw new Error(MessageManager.getString("error.multiple_jnet_subjob_merge_not_implemented"));
569             }
570           } catch (Exception e)
571           {
572             jalview.bin.Cache.log.error(
573                     "JNet Client: JPred Annotation Parse Error", e);
574             wsInfo.setStatus(j.getJobnum(),
575                     WebserviceInfo.STATE_STOPPED_ERROR);
576             wsInfo.appendProgressText(j.getJobnum(), MessageManager.formatMessage("info.invalid_jnet_job_result_data", new String[]{OutputHeader.toString(),j.result.getStatus(), e.getMessage() }));
577             j.result.setBroken(true);
578           }
579         }
580       }
581
582       if (res != null)
583       {
584         if (newWindow)
585         {
586           AlignFrame af;
587           if (input == null)
588           {
589             if (res[1] != null)
590             {
591               af = new AlignFrame((Alignment) res[0],
592                       (ColumnSelection) res[1], AlignFrame.DEFAULT_WIDTH,
593                       AlignFrame.DEFAULT_HEIGHT);
594             }
595             else
596             {
597               af = new AlignFrame((Alignment) res[0],
598                       AlignFrame.DEFAULT_WIDTH, AlignFrame.DEFAULT_HEIGHT);
599             }
600           }
601           else
602           {
603             /*
604              * java.lang.Object[] alandcolsel =
605              * input.getAlignmentAndColumnSelection
606              * (alignFrame.getViewport().getGapCharacter()); if
607              * (((SequenceI[])alandcolsel[0])[0].getLength()!=res.getWidth()) {
608              * if (msa) { throw new Error("Implementation Error! ColumnSelection
609              * from input alignment will not map to result alignment!"); } } if
610              * (!msa) { // update hidden regions to account for loss of gaps in
611              * profile. - if any // gapMap returns insert list, interpreted as
612              * delete list by pruneDeletions //((ColumnSelection)
613              * alandcolsel[1]).pruneDeletions(ShiftList.parseMap(((SequenceI[])
614              * alandcolsel[0])[0].gapMap())); }
615              */
616
617             af = new AlignFrame((Alignment) res[0],
618                     (ColumnSelection) res[1], AlignFrame.DEFAULT_WIDTH,
619                     AlignFrame.DEFAULT_HEIGHT);
620           }
621           Desktop.addInternalFrame(af, altitle, AlignFrame.DEFAULT_WIDTH,
622                   AlignFrame.DEFAULT_HEIGHT);
623         }
624         else
625         {
626           Cache.log.info("Append results onto existing alignment.");
627         }
628       }
629     }
630   }
631
632   public void pollJob(AWsJob job) throws Exception
633   {
634     ((JPredJob) job).result = server.getresult(job.getJobId());
635   }
636
637   public boolean isCancellable()
638   {
639     return false;
640   }
641
642   public void cancelJob()
643   {
644     throw new Error(MessageManager.getString("error.implementation_error"));
645   }
646
647   public boolean canMergeResults()
648   {
649     return false;
650   }
651
652 }