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