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