JAL-3899 Update usages of uniquify and deuniquify.
[jalview.git] / src / jalview / ws / jws2 / JPredThread.java
1 package jalview.ws.jws2;
2
3 import static java.lang.String.format;
4
5 import java.util.Hashtable;
6 import java.util.List;
7 import java.util.Map;
8
9 import jalview.analysis.SeqsetUtils;
10 import jalview.analysis.SeqsetUtils.SequenceInfo;
11 import jalview.bin.Cache;
12 import jalview.commands.RemoveGapsCommand;
13 import jalview.datamodel.Alignment;
14 import jalview.datamodel.AlignmentAnnotation;
15 import jalview.datamodel.AlignmentI;
16 import jalview.datamodel.AlignmentView;
17 import jalview.datamodel.HiddenColumns;
18 import jalview.datamodel.SequenceI;
19 import jalview.gui.AlignFrame;
20 import jalview.gui.Desktop;
21 import jalview.gui.WebserviceInfo;
22 import jalview.io.JnetAnnotationMaker;
23 import jalview.util.MessageManager;
24 import jalview.ws.AWSThread;
25 import jalview.ws.AWsJob;
26 import jalview.ws.JobStateSummary;
27 import jalview.ws.WSClientI;
28 import jalview.ws.api.CancellableI;
29 import jalview.ws.api.JPredServiceI;
30 import jalview.ws.gui.WsJob;
31 import jalview.ws.gui.WsJob.JobState;
32
33
34 public class JPredThread extends AWSThread implements WSClientI
35 {
36
37   private static class JPredJob extends WsJob
38   {
39     private final Object sequenceInfo;
40     private final List<SequenceI> msf;
41     private final int[] delMap;
42     private AlignmentI alignment = null;
43     private HiddenColumns hiddenCols = null;
44
45     private JPredJob(Object sequenceInfo, SequenceI[] msf, int[] delMap)
46     {
47       this.sequenceInfo = sequenceInfo;
48       this.msf = List.of(msf);
49       this.delMap = delMap;
50     }
51
52     @Override
53     public boolean hasValidInput()
54     {
55       return true;
56     }
57
58     @Override
59     public boolean hasResults()
60     {
61       return (isSubjobComplete() && alignment != null);
62     }
63
64     public boolean isMSA()
65     {
66       return msf.size() > 1;
67     }
68   }
69
70
71   private JPredServiceI server;
72   private String title;
73   private Object sequenceInfo;
74   private SequenceI[] msf;
75   private int[] delMap;
76
77   public JPredThread(WebserviceInfo wsInfo, String title,
78       JPredServiceI server, Object sequenceInfo,
79       SequenceI[] msf, int[] delMap, AlignmentView view, AlignFrame frame,
80       String wsURL)
81   {
82     super(frame, wsInfo, view, wsURL);
83     this.server = server;
84     this.title = title;
85     this.sequenceInfo = sequenceInfo;
86     this.msf = msf;
87     this.delMap = delMap;
88     JPredJob job = new JPredJob(sequenceInfo, msf, delMap);
89     this.jobs = new JPredJob[] { job };
90   }
91
92   @Override
93   public boolean isCancellable()
94   {
95     return server instanceof CancellableI;
96   }
97
98   @Override
99   public boolean canMergeResults()
100   {
101     return false;
102   }
103
104   @Override
105   public void cancelJob()
106   {
107     // TODO Auto-generated method stub
108
109   }
110
111   @Override
112   public void pollJob(AWsJob job_) throws Exception
113   {
114     var job = (JPredJob) job_;
115     server.updateStatus(job);
116     server.updateJobProgress(job);
117   }
118
119   @Override
120   public void StartJob(AWsJob job_)
121   {
122     if (!(job_ instanceof JPredJob))
123       throw new RuntimeException("Invalid job type");
124     var job = (JPredJob) job_;
125     if (job.isSubmitted())
126     {
127       return;
128     }
129     try {
130       try
131       {
132         var jobHandle = server.predict(job.msf, job.isMSA());
133         if (jobHandle != null)
134           job.setJobHandle(jobHandle);
135       }
136       catch (Throwable th) {
137         if (!server.handleSubmitError(th, job, wsInfo)) {
138           throw th;
139         }
140       }
141       if (job.getJobId() != null) {
142         job.setSubmitted(true);
143         job.setSubjobComplete(false);
144         return;
145       }
146       else {
147         throw new Exception(MessageManager.formatMessage(
148                 "exception.web_service_returned_null_try_later",
149                 new String[]
150                 { WsUrl }));
151       }
152     }
153     catch (Throwable th)
154     {
155       // For unexpected errors
156       System.err.println(WebServiceName
157               + "Client: Failed to submit the sequences for alignment (probably a server side problem)\n"
158               + "When contacting Server:" + WsUrl + "\n");
159       th.printStackTrace(System.err);
160       wsInfo.setStatus(WebserviceInfo.STATE_STOPPED_SERVERERROR);
161       wsInfo.setStatus(job.getJobnum(),
162               WebserviceInfo.STATE_STOPPED_SERVERERROR);
163
164     }
165     finally
166     {
167       if (!job.isSubmitted())
168       {
169         job.setAllowedServerExceptions(0);
170         wsInfo.appendProgressText(job.getJobnum(), MessageManager.getString(
171                 "info.failed_to_submit_sequences_for_alignment"));
172       }
173     }
174   }
175
176   @Override
177   public void parseResult()
178   {
179     long progbar = (long) (Math.random() * ~(1L << 63));
180     wsInfo.setProgressBar(
181         MessageManager.getString("status.collecting_job_results"), progbar);
182     int results = 0;
183     var finalState = new JobStateSummary();
184     try
185     {
186       for (int i = 0; i < jobs.length; i++) {
187         final var job = (JPredJob) jobs[i];
188         finalState.updateJobPanelState(wsInfo, OutputHeader, job);
189         if (job.isFinished()) {
190           try {
191             server.updateJobProgress(job);
192           }
193           catch (Exception e) {
194             Cache.log.warn(format(
195                 "Exception when retrieving remaining Job progress data " +
196                 "for job %s on server %s", job.getJobId(), WsUrl));
197             e.printStackTrace();
198           }
199           // removed the waiting loop
200           Cache.log.debug(format("Job Execution file for job: %s " +
201               "on server %s%n%s", job.getJobId(), WsUrl, job.getStatus()));
202           try {
203             prepareJobResult(job);
204           }
205           catch (Exception e) {
206             if (!server.handleCollectionException(e, job, wsInfo)) {
207               Cache.log.error("Could not get alignment for job.", e);
208               job.setState(JobState.SERVERERROR);
209             }
210           }
211         }
212         finalState.updateJobPanelState(wsInfo, OutputHeader, job);
213         if (job.isSubmitted() && job.isSubjobComplete() && job.hasResults()) {
214           results++;
215         }
216       }
217     }
218     catch (Exception e) {
219       Cache.log.error(
220           "Unexpected exception when processing results for " + title, e);
221       wsInfo.setStatus(WebserviceInfo.STATE_STOPPED_ERROR);
222     }
223     if (results > 0) {
224       wsInfo.showResultsNewFrame.addActionListener(
225           (evt) -> displayResults(true));
226       wsInfo.mergeResults.addActionListener(
227           (evt) -> displayResults(false));
228       wsInfo.setResultsReady();
229     }
230     else {
231       wsInfo.setStatus(WebserviceInfo.STATE_STOPPED_ERROR);
232       wsInfo.appendInfoText("No jobs ran.");
233       wsInfo.setFinishedNoResults();
234     }
235     updateGlobalStatus(finalState);
236     wsInfo.removeProgressBar(progbar);
237   }
238
239   static final int msaIndex = 0;
240
241   private void prepareJobResult(JPredJob job) throws Exception
242   {
243     HiddenColumns hiddenCols = null;
244     int firstSeq = -1;
245     AlignmentI alignment;
246     var prediction = server.getPrediction(job.getJobHandle());
247     var preds = prediction.getSeqsAsArray();
248
249     if (job.msf.size() > 1)
250     {
251       if (job.delMap != null)
252       {
253         Object[] alandcolsel = input
254                 .getAlignmentAndHiddenColumns(getGapChar());
255         alignment = new Alignment((SequenceI[]) alandcolsel[0]);
256         hiddenCols = (HiddenColumns) alandcolsel[1];
257       }
258       else
259       {
260         alignment = server.getAlignment(job.getJobHandle());
261         var seqs = new SequenceI[alignment.getHeight()];
262         for (int i = 0; i < alignment.getHeight(); i++)
263         {
264           seqs[i] = alignment.getSequenceAt(i);
265         }
266         if (!SeqsetUtils.deuniquify((Map<String, SequenceInfo>)sequenceInfo, seqs))
267         {
268           throw (new Exception(MessageManager.getString(
269                   "exception.couldnt_recover_sequence_properties_for_alignment")));
270         }
271       }
272       firstSeq = 0;
273       if (currentView.getDataset() != null)
274       {
275         alignment.setDataset(currentView.getDataset());
276       }
277       else
278       {
279         alignment.setDataset(null);
280       }
281       JnetAnnotationMaker.add_annotation(prediction, alignment, firstSeq, false,
282               job.delMap);
283     }
284     else
285     {
286       alignment = new Alignment(preds);
287       firstSeq = prediction.getQuerySeqPosition();
288       if (job.delMap != null)
289       {
290         Object[] alanndcolsel = input.getAlignmentAndHiddenColumns(getGapChar());
291         SequenceI[] seqs = (SequenceI[]) alanndcolsel[0];
292         new RemoveGapsCommand(MessageManager.getString("label.remove_gaps"),
293                 new SequenceI[] {seqs[msaIndex]}, currentView);
294         SequenceI profileSeq = alignment.getSequenceAt(firstSeq);
295         profileSeq.setSequence(seqs[msaIndex].getSequenceAsString());
296       }
297       if (!SeqsetUtils.SeqCharacterUnhash(
298               alignment.getSequenceAt(firstSeq), (SequenceInfo)sequenceInfo))
299       {
300         throw new Exception(MessageManager.getString(
301                 "exception.couldnt_recover_sequence_props_for_jnet_query"));
302       }
303       alignment.setDataset(currentView.getDataset());
304       JnetAnnotationMaker.add_annotation(prediction, alignment, firstSeq, true,
305               job.delMap);
306       SequenceI profileSeq = alignment.getSequenceAt(0);
307       alignToProfileSeq(alignment, profileSeq);
308       if (job.delMap != null)
309       {
310         hiddenCols = alignment.propagateInsertions(profileSeq, input);
311       }
312     }
313
314     for (var annot : alignment.getAlignmentAnnotation())
315     {
316       if (annot.sequenceRef != null)
317       {
318         replaceAnnotationOnAlignmentWith(annot, annot.label,
319                 "jalview.ws.JPred", annot.sequenceRef);
320       }
321     }
322     job.alignment = alignment;
323     job.hiddenCols = hiddenCols;
324   }
325
326   private static void replaceAnnotationOnAlignmentWith(
327           AlignmentAnnotation newAnnot, String typeName, String calcId,
328           SequenceI aSeq)
329   {
330     SequenceI dsseq = aSeq.getDatasetSequence();
331     while (dsseq.getDatasetSequence() != null)
332     {
333       dsseq = dsseq.getDatasetSequence();
334     }
335     // look for same annotation on dataset and lift this one over
336     List<AlignmentAnnotation> dsan = dsseq.getAlignmentAnnotations(calcId,
337             typeName);
338     if (dsan != null && dsan.size() > 0)
339     {
340       for (AlignmentAnnotation dssan : dsan)
341       {
342         dsseq.removeAlignmentAnnotation(dssan);
343       }
344     }
345     AlignmentAnnotation dssan = new AlignmentAnnotation(newAnnot);
346     dsseq.addAlignmentAnnotation(dssan);
347     dssan.adjustForAlignment();
348   }
349
350   private static void alignToProfileSeq(AlignmentI al, SequenceI profileseq)
351   {
352     char gc = al.getGapCharacter();
353     int[] gapMap = profileseq.gapMap();
354     // insert gaps into profile
355     for (int lp = 0, r = 0; r < gapMap.length; r++)
356     {
357       if (gapMap[r] - lp > 1)
358       {
359         StringBuffer sb = new StringBuffer();
360         for (int s = 0, ns = gapMap[r] - lp; s < ns; s++)
361         {
362           sb.append(gc);
363         }
364         for (int s = 1, ns = al.getHeight(); s < ns; s++)
365         {
366           String sq = al.getSequenceAt(s).getSequenceAsString();
367           int diff = gapMap[r] - sq.length();
368           if (diff > 0)
369           {
370             // pad gaps
371             sq = sq + sb;
372             while ((diff = gapMap[r] - sq.length()) > 0)
373             {
374               sq = sq + ((diff >= sb.length()) ? sb.toString()
375                       : sb.substring(0, diff));
376             }
377             al.getSequenceAt(s).setSequence(sq);
378           }
379           else
380           {
381             al.getSequenceAt(s).setSequence(sq.substring(0, gapMap[r])
382                     + sb.toString() + sq.substring(gapMap[r]));
383           }
384         }
385       }
386       lp = gapMap[r];
387     }
388   }
389
390   private void displayResults(boolean newWindow)
391   {
392     if (jobs == null || jobs.length == 0)
393     {
394       return;
395     }
396     var job = (JPredJob) jobs[0];
397     if (job.hasResults() && newWindow)
398     {
399       job.alignment.setSeqrep(job.alignment.getSequenceAt(0));
400       AlignFrame frame = new AlignFrame(job.alignment, job.hiddenCols,
401               AlignFrame.DEFAULT_WIDTH, AlignFrame.DEFAULT_HEIGHT);
402       Desktop.addInternalFrame(frame, title, AlignFrame.DEFAULT_WIDTH,
403               AlignFrame.DEFAULT_HEIGHT);
404     }
405   }
406
407 }