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