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