JAL-3878 update branch from 2.12 merge from 2.11.2
[jalview.git] / src / jalview / ws2 / gui / AlignmentServiceGuiHandler.java
1 package jalview.ws2.gui;
2
3 import static java.util.Objects.requireNonNullElse;
4
5 import java.util.ArrayList;
6 import java.util.Arrays;
7 import java.util.BitSet;
8 import java.util.HashMap;
9 import java.util.LinkedHashMap;
10 import java.util.LinkedList;
11 import java.util.List;
12
13 import javax.swing.JInternalFrame;
14 import javax.swing.SwingUtilities;
15
16 import jalview.bin.Cache;
17 import jalview.bin.Console;
18 import jalview.datamodel.Alignment;
19 import jalview.datamodel.AlignmentI;
20 import jalview.datamodel.AlignmentOrder;
21 import jalview.datamodel.HiddenColumns;
22 import jalview.gui.AlignFrame;
23 import jalview.gui.Desktop;
24 import jalview.gui.JvOptionPane;
25 import jalview.gui.SplitFrame;
26 import jalview.gui.WebserviceInfo;
27 import jalview.util.ArrayUtils;
28 import jalview.util.MessageManager;
29 import jalview.util.Pair;
30 import jalview.ws2.actions.alignment.AlignmentAction;
31 import jalview.ws2.actions.alignment.AlignmentResult;
32 import jalview.ws2.actions.api.JobI;
33 import jalview.ws2.actions.api.TaskEventListener;
34 import jalview.ws2.actions.api.TaskI;
35 import jalview.ws2.api.JobStatus;
36 import jalview.ws2.api.WebService;
37 import jalview.ws2.helpers.WSClientTaskWrapper;
38
39 class AlignmentServiceGuiHandler
40     implements TaskEventListener<AlignmentResult>
41 {
42   private final WebService<?> service;
43
44   private final AlignFrame frame;
45
46   private WebserviceInfo infoPanel;
47
48   private String alnTitle; // title of the alignment used in new window
49
50   private JobI[] jobs = new JobI[0];
51
52   private int[] tabs = new int[0];
53
54   private int[] logOffset = new int[0];
55
56   private int[] errLogOffset = new int[0];
57
58   public AlignmentServiceGuiHandler(AlignmentAction action, AlignFrame frame)
59   {
60     this.service = action.getWebService();
61     this.frame = frame;
62     String panelInfo = String.format("%s using service hosted at %s%n%s",
63         service.getName(), service.getUrl(), service.getDescription());
64     infoPanel = new WebserviceInfo(service.getName(), panelInfo, false);
65     String actionName = requireNonNullElse(action.getName(), "Alignment");
66     alnTitle = String.format("%s %s of %s", service.getName(), actionName,
67         frame.getTitle());
68   }
69
70   @Override
71   public void taskStatusChanged(TaskI<AlignmentResult> source, JobStatus status)
72   {
73     switch (status)
74     {
75     case INVALID:
76       infoPanel.setVisible(false);
77       JvOptionPane.showMessageDialog(frame,
78           MessageManager.getString("info.invalid_msa_input_mininfo"),
79           MessageManager.getString("info.invalid_msa_notenough"),
80           JvOptionPane.INFORMATION_MESSAGE);
81       break;
82     case READY:
83       infoPanel.setthisService(new WSClientTaskWrapper(source));
84       infoPanel.setVisible(true);
85       // intentional no break
86     case SUBMITTED:
87     case QUEUED:
88       infoPanel.setStatus(WebserviceInfo.STATE_QUEUING);
89       break;
90     case RUNNING:
91     case UNKNOWN: // unsure what to do with unknown
92       infoPanel.setStatus(WebserviceInfo.STATE_RUNNING);
93       break;
94     case COMPLETED:
95       infoPanel.setProgressBar(
96           MessageManager.getString("status.collecting_job_results"),
97           jobs[0].getInternalId());
98       infoPanel.setStatus(WebserviceInfo.STATE_STOPPED_OK);
99       break;
100     case FAILED:
101       infoPanel.removeProgressBar(jobs[0].getInternalId());
102       infoPanel.setStatus(WebserviceInfo.STATE_STOPPED_ERROR);
103       break;
104     case CANCELLED:
105       infoPanel.setStatus(WebserviceInfo.STATE_CANCELLED_OK);
106       break;
107     case SERVER_ERROR:
108       infoPanel.removeProgressBar(jobs[0].getInternalId());
109       infoPanel.setStatus(WebserviceInfo.STATE_STOPPED_SERVERERROR);
110       break;
111     }
112   }
113
114   @Override
115   public void taskStarted(TaskI<AlignmentResult> source, List<? extends JobI> subJobs)
116   {
117     jobs = subJobs.toArray(new JobI[0]);
118     tabs = new int[subJobs.size()];
119     logOffset = new int[subJobs.size()];
120     errLogOffset = new int[subJobs.size()];
121     for (int i = 0; i < subJobs.size(); i++)
122     {
123       JobI job = jobs[i];
124       int tabIndex = infoPanel.addJobPane();
125       tabs[i] = tabIndex;
126       infoPanel.setProgressName(String.format("region %d", i), tabIndex);
127       infoPanel.setProgressText(tabIndex, alnTitle + "\nJob details\n");
128       // jobs should not have states other than invalid or ready at this point
129       if (job.getStatus() == JobStatus.INVALID)
130         infoPanel.setStatus(tabIndex, WebserviceInfo.STATE_STOPPED_OK);
131       else if (job.getStatus() == JobStatus.READY)
132         infoPanel.setStatus(tabIndex, WebserviceInfo.STATE_QUEUING);
133     }
134   }
135
136   @Override
137   public void taskCompleted(TaskI<AlignmentResult> source, AlignmentResult result)
138   {
139     SwingUtilities.invokeLater(() -> infoPanel.removeProgressBar(jobs[0].getInternalId()));
140     if (result == null)
141     {
142       SwingUtilities.invokeLater(infoPanel::setFinishedNoResults);
143       return;
144     }
145     infoPanel.showResultsNewFrame.addActionListener(evt -> {
146       var aln = result.getAlignment();
147       // copy alignment for each frame to have its own isntance
148       var alnCpy = new Alignment(aln);
149       alnCpy.setGapCharacter(aln.getGapCharacter());
150       alnCpy.setDataset(aln.getDataset());
151       displayResultsNewFrame(alnCpy, result.getAlignmentOrders(),
152           result.getHiddenColumns());
153     });
154     SwingUtilities.invokeLater(infoPanel::setResultsReady);
155   }
156
157   private void displayResultsNewFrame(Alignment aln,
158       List<AlignmentOrder> alorders, HiddenColumns hidden)
159   {
160     AlignFrame newFrame = new AlignFrame(aln, hidden, AlignFrame.DEFAULT_WIDTH,
161         AlignFrame.DEFAULT_HEIGHT);
162     newFrame.getFeatureRenderer().transferSettings(
163         frame.getFeatureRenderer().getSettings());
164     if (alorders.size() > 0)
165     {
166       addSortByMenuItems(newFrame, alorders);
167     }
168
169     var requestingFrame = frame;
170     var splitContainer = requestingFrame.getSplitViewContainer();
171     if (splitContainer != null && splitContainer.getComplement(requestingFrame) != null)
172     {
173       AlignmentI complement = splitContainer.getComplement(requestingFrame);
174       String complementTitle = splitContainer.getComplementTitle(requestingFrame);
175       Alignment copyComplement = new Alignment(complement);
176       copyComplement.setGapCharacter(complement.getGapCharacter());
177       copyComplement.setDataset(complement.getDataset());
178       copyComplement.alignAs(aln);
179       if (copyComplement.getHeight() > 0)
180       {
181         newFrame.setTitle(alnTitle);
182         AlignFrame newFrame2 = new AlignFrame(copyComplement,
183             AlignFrame.DEFAULT_WIDTH, AlignFrame.DEFAULT_HEIGHT);
184         newFrame2.setTitle(complementTitle);
185         String linkedTitle = MessageManager.getString("label.linked_view_title");
186         JInternalFrame splitFrame = new SplitFrame(
187             aln.isNucleotide() ? newFrame : newFrame2,
188             aln.isNucleotide() ? newFrame2 : newFrame);
189         Desktop.addInternalFrame(splitFrame, linkedTitle, -1, -1);
190         return;
191       }
192     }
193     // no split frame or failed to create complementary alignment
194     Desktop.addInternalFrame(newFrame, alnTitle, AlignFrame.DEFAULT_WIDTH,
195         AlignFrame.DEFAULT_HEIGHT);
196   }
197   
198   private void addSortByMenuItems(AlignFrame frame, List<AlignmentOrder> alorders)
199   {
200     if (alorders.size() == 1)
201     {
202       frame.addSortByOrderMenuItem(service.getName() + " Ordering",
203           alorders.get(0));
204       return;
205     }
206     BitSet collected = new BitSet(alorders.size());
207     for (int i = 0, N = alorders.size(); i < N; i++)
208     {
209       if (collected.get(i))
210         continue;
211       var regions = new ArrayList<String>();
212       var order = alorders.get(i);
213       for (int j = i; j < N; j++)
214       {
215         if (!collected.get(j) && alorders.get(j).equals(order))
216         {
217           regions.add(Integer.toString(j + 1));
218           collected.set(j);
219         }
220       }
221       var orderName = String.format("%s Region %s Ordering",
222           service.getName(), String.join(",", regions));
223       frame.addSortByOrderMenuItem(orderName, order);
224     }
225   }
226
227   @Override
228   public void taskException(TaskI<AlignmentResult> source, Exception e)
229   {
230     Console.error(String.format("Service %s raised an exception.", service.getName()), e);
231     infoPanel.appendProgressText(e.getMessage());
232   }
233
234   @Override
235   public void taskRestarted(TaskI<AlignmentResult> source)
236   {
237     // alignment services are not restartable
238   }
239
240   @Override
241   public void subJobStatusChanged(TaskI<AlignmentResult> source, JobI job, JobStatus status)
242   {
243     int i = ArrayUtils.indexOf(jobs, job);
244     assert i >= 0 : "job does not exist";
245     if (i < 0)
246       // safeguard that should not happen irl
247       return;
248     int wsStatus;
249     switch (status)
250     {
251     case INVALID:
252     case COMPLETED:
253       wsStatus = WebserviceInfo.STATE_STOPPED_OK;
254       break;
255     case READY:
256     case SUBMITTED:
257     case QUEUED:
258       wsStatus = WebserviceInfo.STATE_QUEUING;
259       break;
260     case RUNNING:
261     case UNKNOWN:
262       wsStatus = WebserviceInfo.STATE_RUNNING;
263       break;
264     case FAILED:
265       wsStatus = WebserviceInfo.STATE_STOPPED_ERROR;
266       break;
267     case CANCELLED:
268       wsStatus = WebserviceInfo.STATE_CANCELLED_OK;
269       break;
270     case SERVER_ERROR:
271       wsStatus = WebserviceInfo.STATE_STOPPED_SERVERERROR;
272       break;
273     default:
274       throw new AssertionError("Non-exhaustive switch statement");
275     }
276     infoPanel.setStatus(tabs[i], wsStatus);
277   }
278
279   @Override
280   public void subJobLogChanged(TaskI<AlignmentResult> source, JobI job, String log)
281   {
282     int i = ArrayUtils.indexOf(jobs, job);
283     assert i >= 0 : "job does not exist";
284     if (i < 0)
285       // safeguard that should never happen
286       return;
287     infoPanel.appendProgressText(tabs[i], log.substring(logOffset[i]));
288   }
289
290   @Override
291   public void subJobErrorLogChanged(TaskI<AlignmentResult> source, JobI job, String log)
292   {
293     int i = ArrayUtils.indexOf(jobs, job);
294     assert i >= 0 : "job does not exist";
295     if (i < 0)
296       // safeguard that should never happen
297       return;
298     infoPanel.appendProgressText(tabs[i], log.substring(errLogOffset[i]));
299   }
300
301 }