Clover icon

Coverage Report

  1. Project Clover database Thu Dec 4 2025 14:43:25 GMT
  2. Package jalview.ws2.gui

File WebServicesMenuManager.java

 

Coverage histogram

../../../img/srcFileCovDistChart6.png
37% of files have more coverage

Code metrics

78
255
19
2
535
484
68
0.27
13.42
9.5
3.58

Classes

Class Line # Actions
WebServicesMenuManager 51 171 54
0.551020455.1%
WebServicesMenuManager.InteractiveServiceEntryGroup 278 84 14
0.4579439245.8%
 

Contributing tests

This file is covered by 448 tests. .

Source view

1    package jalview.ws2.gui;
2   
3    import java.awt.Color;
4    import java.awt.event.MouseAdapter;
5    import java.awt.event.MouseEvent;
6    import java.net.URL;
7    import java.util.ArrayList;
8    import java.util.Collections;
9    import java.util.Comparator;
10    import java.util.HashMap;
11    import java.util.HashSet;
12    import java.util.List;
13    import java.util.Map;
14    import java.util.TreeMap;
15    import java.util.concurrent.CompletionStage;
16   
17    import javax.swing.JCheckBoxMenuItem;
18    import javax.swing.JLabel;
19    import javax.swing.JMenu;
20    import javax.swing.JMenuItem;
21    import javax.swing.ToolTipManager;
22    import javax.swing.border.EmptyBorder;
23   
24    import jalview.bin.Console;
25    import jalview.datamodel.AlignmentI;
26    import jalview.gui.AlignFrame;
27    import jalview.gui.Desktop;
28    import jalview.gui.JvSwingUtils;
29    import jalview.gui.WsJobParameters;
30    import jalview.util.MessageManager;
31    import jalview.viewmodel.AlignmentViewport;
32    import jalview.ws.params.ArgumentI;
33    import jalview.ws.params.ParamDatastoreI;
34    import jalview.ws.params.WsParamSetI;
35    import jalview.ws2.actions.BaseAction;
36    import jalview.ws2.actions.BaseTask;
37    import jalview.ws2.actions.PollingTaskExecutor;
38    import jalview.ws2.actions.alignment.AlignmentAction;
39    import jalview.ws2.actions.alignment.AlignmentResult;
40    import jalview.ws2.actions.annotation.AlignCalcWorkerAdapter;
41    import jalview.ws2.actions.annotation.AnnotationAction;
42    import jalview.ws2.actions.api.ActionI;
43    import jalview.ws2.actions.api.TaskEventListener;
44    import jalview.ws2.actions.api.TaskI;
45    import jalview.ws2.actions.hmmer.PhmmerAction;
46    import jalview.ws2.actions.secstructpred.SecStructPredAction;
47    import jalview.ws2.api.Credentials;
48    import jalview.ws2.api.WebService;
49    import jalview.ws2.client.api.WebServiceProviderI;
50   
 
51    public class WebServicesMenuManager
52    {
53    private final JMenu menu;
54   
55    private final AlignFrame frame;
56   
57    private JMenuItem inProgressItem = new JMenuItem("Service discovery in progress");
58   
59    private JMenuItem noServicesItem = new JMenuItem("No services available");
 
60  1461 toggle {
61  1461 inProgressItem.setEnabled(false);
62  1461 inProgressItem.setVisible(false);
63  1461 noServicesItem.setEnabled(false);
64    }
65   
 
66  1461 toggle public WebServicesMenuManager(String name, AlignFrame frame)
67    {
68  1461 this.frame = frame;
69  1461 menu = new JMenu(name);
70  1461 menu.add(inProgressItem);
71  1461 menu.add(noServicesItem);
72    }
73   
 
74  24687 toggle public JMenu getMenu()
75    {
76  24687 return menu;
77    }
78   
 
79  41593 toggle public void setNoServices(boolean noServices)
80    {
81  41593 noServicesItem.setVisible(noServices);
82    }
83   
 
84  41593 toggle public void setInProgress(boolean inProgress)
85    {
86  41593 inProgressItem.setVisible(inProgress);
87    }
88   
 
89  41593 toggle public void setServices(WebServiceProviderI services)
90    {
91  41593 menu.removeAll();
92    // services grouped by their category
93  41593 Map<String, List<WebService<?>>> oneshotServices = new HashMap<>();
94  41593 Map<String, List<WebService<?>>> interactiveServices = new HashMap<>();
95  41593 for (WebService<?> service : services.getServices())
96    {
97  71127 var map = service.isInteractive() ? interactiveServices : oneshotServices;
98  71127 map.computeIfAbsent(service.getCategory(), k -> new ArrayList<>())
99    .add(service);
100    }
101  41593 var allKeysSet = new HashSet<>(oneshotServices.keySet());
102  41592 allKeysSet.addAll(interactiveServices.keySet());
103  41592 var allKeys = new ArrayList<>(allKeysSet);
104  41593 allKeys.sort(Comparator.naturalOrder());
105  41592 for (String category : allKeys)
106    {
107  36252 var categoryMenu = new JMenu(category);
108  36252 var oneshot = oneshotServices.get(category);
109  36252 if (oneshot != null)
110  28502 addOneshotEntries(oneshot, categoryMenu);
111  36252 var interactive = interactiveServices.get(category);
112  36252 if (interactive != null)
113    {
114  7750 if (oneshot != null)
115  0 categoryMenu.addSeparator();
116  7750 addInteractiveEntries(interactive, categoryMenu);
117    }
118  36252 menu.add(categoryMenu);
119    }
120  41593 menu.add(inProgressItem);
121  41593 menu.add(noServicesItem);
122    }
123   
 
124  28502 toggle private void addOneshotEntries(List<WebService<?>> services, JMenu menu)
125    {
126    // Workaround. Comparator methods not working in j2s
127  28502 services.sort((ws1, ws2) -> {
128  77499 var res = ws1.getUrl().toString().compareTo(ws2.getUrl().toString());
129  77500 if (res == 0)
130  77500 res = ws1.getName().compareTo(ws2.getName());
131  77500 return res;
132    });
133  28502 URL lastHost = null;
134  28502 for (WebService<?> service : services)
135    {
136    // if new host differs from the last one, add entry separating them
137  63377 URL host = service.getUrl();
138  63377 if (!host.equals(lastHost))
139    {
140  28502 if (lastHost != null)
141  0 menu.addSeparator();
142  28502 var item = new JMenuItem(host.toString());
143  28502 item.setForeground(Color.BLUE);
144  28502 item.addActionListener(e -> Desktop.showUrl(host.toString()));
145  28502 menu.add(item);
146  28502 lastHost = host;
147    }
148  63377 menu.addSeparator();
149    // group actions by their subcategory, sorted
150  63377 var actionsByCategory = new TreeMap<String, List<ActionI<?>>>();
151  63377 for (ActionI<?> action : service.getActions())
152    {
153  80433 actionsByCategory
154    .computeIfAbsent(
155  80433 action.getSubcategory() != null ? action.getSubcategory() : "",
156    k -> new ArrayList<>())
157    .add(action);
158    }
159  63377 for (var entry : actionsByCategory.entrySet())
160    {
161  71126 var category = entry.getKey();
162  71127 var actions = entry.getValue();
163    // create submenu named {subcategory} with {service} or use root menu
164  71127 var atMenu = category.isEmpty() ? menu : new JMenu(String.format("%s with %s", category, service.getName()));
165  71127 if (atMenu != menu)
166  15500 menu.add(atMenu); // add only if submenu
167    // sort actions by name pulling nulls to the front
168  71126 actions.sort(Comparator.comparing(
169    ActionI::getName, Comparator.nullsFirst(Comparator.naturalOrder())));
170  151558 for (int i = 0; i < actions.size(); i++)
171    {
172  80432 addEntriesForAction(actions.get(i), atMenu, atMenu == menu);
173    }
174    }
175    }
176    }
177   
 
178  80432 toggle private void addEntriesForAction(ActionI<?> action, JMenu menu, boolean isTopLevel)
179    {
180  80432 var enabled = isActionEnabled(action);
181  80431 var service = action.getWebService();
182  80431 String itemName;
183  80432 if (isTopLevel)
184    {
185  64932 itemName = service.getName();
186  64931 if (action.getName() != null && !action.getName().isEmpty())
187  53485 itemName += " " + action.getName();
188    }
189    else
190    {
191  15500 if (action.getName() == null || action.getName().isEmpty())
192  0 itemName = "Run";
193    else
194  15500 itemName = action.getName();
195    }
196  80430 var datastore = service.getParamDatastore();
197    {
198  80430 String text = itemName;
199  80432 if (datastore.hasParameters() || datastore.hasPresets())
200  50195 text += " with defaults";
201  80432 JMenuItem item = new JMenuItem(text);
202  80433 item.setEnabled(enabled);
203  80433 item.addActionListener(e -> {
204  0 runAction(action, frame.getCurrentView(), Collections.emptyList(),
205    Credentials.empty());
206    });
207  80433 menu.add(item);
208    }
209  80433 if (datastore.hasParameters())
210    {
211  50196 JMenuItem item = new JMenuItem("Edit settings and run...");
212  50196 item.setEnabled(enabled);
213  50196 item.addActionListener(e -> {
214  0 openEditParamsDialog(datastore, null, null).thenAccept(args -> {
215  0 if (args != null)
216  0 runAction(action, frame.getCurrentView(), args, Credentials.empty());
217    });
218    });
219  50196 menu.add(item);
220    }
221  80433 var presets = datastore.getPresets();
222  80433 if (presets != null && presets.size() > 0)
223    {
224  0 final var presetsMenu = new JMenu(MessageManager.formatMessage(
225    "label.run_with_preset_params", service.getName()));
226  0 presetsMenu.setEnabled(enabled);
227  0 final int dismissDelay = ToolTipManager.sharedInstance()
228    .getDismissDelay();
229  0 final int QUICK_TOOLTIP = 1500;
230  0 for (var preset : presets)
231    {
232  0 var item = new JMenuItem(preset.getName());
233  0 item.addMouseListener(new MouseAdapter()
234    {
 
235  0 toggle @Override
236    public void mouseEntered(MouseEvent evt)
237    {
238  0 ToolTipManager.sharedInstance().setDismissDelay(QUICK_TOOLTIP);
239    }
240   
 
241  0 toggle @Override
242    public void mouseExited(MouseEvent evt)
243    {
244  0 ToolTipManager.sharedInstance().setDismissDelay(dismissDelay);
245    }
246    });
247  0 String tooltipTitle = MessageManager.getString(
248  0 preset.isModifiable() ? "label.user_preset" : "label.service_preset");
249  0 String tooltip = String.format("<strong>%s</strong><br/>%s",
250    tooltipTitle, preset.getDescription());
251  0 tooltip = JvSwingUtils.wrapTooltip(true, tooltip);
252  0 item.setToolTipText(tooltip);
253  0 item.addActionListener(event -> {
254  0 runAction(action, frame.getCurrentView(), preset.getArguments(),
255    Credentials.empty());
256    });
257  0 presetsMenu.add(item);
258    }
259  0 menu.add(presetsMenu);
260    }
261    }
262   
 
263  7750 toggle private void addInteractiveEntries(List<WebService<?>> services, JMenu menu)
264    {
265  7750 Map<String, List<WebService<?>>> byServiceName = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
266  7750 for (var service : services)
267    {
268  7750 byServiceName.computeIfAbsent(service.getName(), k -> new ArrayList<>())
269    .add(service);
270    }
271  7750 for (var entry : byServiceName.entrySet())
272    {
273  7750 var group = new InteractiveServiceEntryGroup(entry.getKey(), entry.getValue());
274  7750 group.appendTo(menu);
275    }
276    }
277   
 
278    private class InteractiveServiceEntryGroup
279    {
280    JLabel serviceLabel;
281   
282    JMenuItem urlItem = new JMenuItem();
283   
284    JCheckBoxMenuItem serviceItem = new JCheckBoxMenuItem();
285   
286    JMenuItem editParamsItem = new JMenuItem("Edit parameters...");
287   
288    JMenu presetsMenu = new JMenu("Change preset");
289   
290    JMenu alternativesMenu = new JMenu("Choose action");
 
291  7750 toggle {
292  7750 urlItem.setForeground(Color.BLUE);
293  7750 urlItem.setVisible(false);
294  7750 serviceItem.setVisible(false);
295  7750 editParamsItem.setVisible(false);
296  7750 presetsMenu.setVisible(false);
297    }
298   
 
299  7750 toggle InteractiveServiceEntryGroup(String name, List<WebService<?>> services)
300    {
301  7750 serviceLabel = new JLabel(name);
302  7750 serviceLabel.setBorder(new EmptyBorder(0, 6, 0, 6));
303  7750 buildAlternativesMenu(services);
304    }
305   
 
306  7750 toggle private void buildAlternativesMenu(List<WebService<?>> services)
307    {
308  7750 var menu = alternativesMenu;
309  7750 services.sort((ws1, ws2) -> {
310  0 var res = ws1.getUrl().toString().compareTo(ws2.getUrl().toString());
311  0 if (res == 0)
312  0 res = ws1.getName().compareTo(ws2.getName());
313  0 return res;
314    });
315  7750 URL lastHost = null;
316  7750 for (var service : services)
317    {
318    // Adding url "separator" before each group
319  7750 URL host = service.getUrl();
320  7750 if (!host.equals(lastHost))
321    {
322  7750 if (lastHost != null)
323  0 menu.addSeparator();
324  7750 var item = new JMenuItem(host.toString());
325  7750 item.setForeground(Color.BLUE);
326  7750 item.addActionListener(e -> Desktop.showUrl(host.toString()));
327  7750 menu.add(item);
328  7750 lastHost = host;
329    }
330  7750 menu.addSeparator();
331  7750 var actionsByCategory = new TreeMap<String, List<ActionI<?>>>();
332  7750 for (ActionI<?> action : service.getActions())
333    {
334  7750 actionsByCategory
335    .computeIfAbsent(
336  7750 action.getSubcategory() != null ? action.getSubcategory() : "",
337    k -> new ArrayList<>())
338    .add(action);
339    }
340  7750 actionsByCategory.forEach((key, actions) -> {
341  7750 var atMenu = key.isEmpty() ? menu : new JMenu(key + " with " + service.getName());
342  7750 boolean topLevel = atMenu == menu;
343  7750 if (!topLevel)
344  0 menu.add(atMenu);
345  7750 actions.sort(Comparator.comparing(
346    a -> a.getName(),
347    Comparator.nullsFirst(Comparator.naturalOrder())));
348  7750 for (ActionI<?> action : actions)
349    {
350  7750 var item = new JMenuItem(action.getFullName());
351  7750 item.setEnabled(isActionEnabled(action));
352  7750 item.addActionListener(e -> setAlternative(action));
353  7750 atMenu.add(item);
354    }
355    });
356    }
357    }
358   
 
359  0 toggle private void setAlternative(ActionI<?> action)
360    {
361  0 final var arguments = new ArrayList<ArgumentI>();
362  0 final WsParamSetI[] lastPreset = { null };
363   
364    // update selected url menu item
365  0 String url = action.getWebService().getUrl().toString();
366  0 urlItem.setText(url);
367  0 urlItem.setVisible(true);
368  0 for (var l : urlItem.getActionListeners())
369  0 urlItem.removeActionListener(l);
370  0 urlItem.addActionListener(e -> Desktop.showUrl(url));
371   
372    // update selected service menu item
373  0 serviceItem.setText(action.getFullName());
374  0 serviceItem.setVisible(true);
375  0 for (var l : serviceItem.getActionListeners())
376  0 serviceItem.removeActionListener(l);
377  0 WebService<?> service = action.getWebService();
378  0 serviceItem.addActionListener(e -> {
379  0 runAction(action, frame.getCurrentView(), arguments,
380    Credentials.empty());
381    });
382  0 serviceItem.setSelected(true);
383   
384    // update edit parameters menu item
385  0 var datastore = service.getParamDatastore();
386  0 editParamsItem.setVisible(datastore.hasParameters());
387  0 for (var l : editParamsItem.getActionListeners())
388  0 editParamsItem.removeActionListener(l);
389  0 if (datastore.hasParameters())
390    {
391  0 editParamsItem.addActionListener(e -> {
392  0 openEditParamsDialog(service.getParamDatastore(), lastPreset[0], arguments)
393    .thenAccept(args -> {
394  0 if (args != null)
395    {
396  0 lastPreset[0] = null;
397  0 arguments.clear();
398  0 arguments.addAll(args);
399  0 runAction(action, frame.getCurrentView(),
400    arguments, Credentials.empty());
401    }
402    });
403    });
404    }
405   
406    // update presets menu
407  0 presetsMenu.removeAll();
408  0 presetsMenu.setEnabled(datastore.hasPresets());
409  0 if (datastore.hasPresets())
410    {
411  0 for (WsParamSetI preset : datastore.getPresets())
412    {
413  0 var item = new JMenuItem(preset.getName());
414  0 item.addActionListener(e -> {
415  0 lastPreset[0] = preset;
416  0 runAction(action, frame.getCurrentView(),
417    preset.getArguments(), Credentials.empty());
418    });
419  0 presetsMenu.add(item);
420    }
421    }
422   
423  0 runAction(action, frame.getCurrentView(), arguments,
424    Credentials.empty());
425    }
426   
 
427  7750 toggle void appendTo(JMenu menu)
428    {
429  7750 menu.add(serviceLabel);
430  7750 menu.add(urlItem);
431  7750 menu.add(serviceItem);
432  7750 menu.add(editParamsItem);
433  7750 menu.add(presetsMenu);
434  7750 menu.add(alternativesMenu);
435    }
436    }
437   
 
438  88182 toggle private boolean isActionEnabled(ActionI<?> action)
439    {
440    // TODO JAL-4107 alignmentFrame's close handler should tidy up these things so this check is no longer necessary
441    // TODO JAL-3878 - shouldn't this be dependent on the datamodel, not the GUI ?
442  88183 if (frame==null || frame.getViewport()==null || frame.getViewport().getAlignment()==null)
443  4039 return false;
444  84144 var isNa = frame.getViewport().getAlignment().isNucleotide();
445  84141 return ((isNa && action.doAllowNucleotide()) ||
446    (!isNa && action.doAllowProtein()));
447    }
448   
 
449  0 toggle private void runAction(ActionI<?> action, AlignmentViewport viewport,
450    List<ArgumentI> args, Credentials credentials)
451    {
452    // casting and instance checks can be avoided with some effort,
453    // let them be for now.
454  0 if (action instanceof AlignmentAction)
455    {
456    // TODO: test if selection contains enough sequences
457  0 var _action = (AlignmentAction) action;
458  0 var handler = new AlignmentServiceGuiHandler(_action, frame);
459  0 BaseTask<?, AlignmentResult> task = _action.createTask(viewport, args, credentials);
460  0 var executor = PollingTaskExecutor.fromPool(viewport.getServiceExecutor());
461  0 task.addTaskEventListener(handler);
462  0 var future = executor.submit(task);
463  0 task.setCancelAction(() -> { future.cancel(true); });
464  0 return;
465    }
466  0 if (action instanceof AnnotationAction)
467    {
468  0 var calcManager = viewport.getCalcManager();
469   
470  0 var _action = (AnnotationAction) action;
471  0 var worker = new AlignCalcWorkerAdapter(viewport, frame.alignPanel,
472    _action, args, credentials);
473  0 var handler = new AnnotationServiceGuiHandler(_action, frame);
474  0 worker.setWorkerListener(handler);
475  0 for (var w : calcManager.getWorkers())
476    {
477  0 if (worker.getCalcName() != null && worker.getCalcName().equals(w.getCalcName()))
478    {
479  0 calcManager.cancelWorker(w);
480  0 calcManager.removeWorker(w);
481    }
482    }
483  0 if (action.getWebService().isInteractive())
484  0 calcManager.registerWorker(worker);
485    else
486  0 calcManager.startWorker(worker);
487  0 return;
488    }
489  0 if (action instanceof PhmmerAction || action instanceof SecStructPredAction)
490    {
491  0 var _action = (BaseAction<AlignmentI>) action;
492  0 var handler = new SearchServiceGuiHandler(_action, frame);
493  0 var task = (BaseTask<?, AlignmentI>) _action // FIXME: unsafe cast
494    .createTask(viewport, args, credentials);
495  0 var executor = PollingTaskExecutor.fromPool(viewport.getServiceExecutor());
496  0 task.addTaskEventListener(handler);
497  0 var future = executor.submit(task);
498  0 task.setCancelAction(() -> {
499  0 future.cancel(true);
500    });
501  0 return;
502    }
503  0 Console.warn(String.format(
504    "No known handler for action type %s. All output will be discarded.",
505    action.getClass().getName()));
506  0 var task = action.createTask(viewport, args, credentials);
507  0 task.addTaskEventListener(TaskEventListener.nullListener());
508  0 PollingTaskExecutor.fromPool(viewport.getServiceExecutor())
509    .submit(task);
510    }
511   
 
512  0 toggle private static CompletionStage<List<ArgumentI>> openEditParamsDialog(
513    ParamDatastoreI paramStore, WsParamSetI preset, List<ArgumentI> arguments)
514    {
515  0 final WsJobParameters jobParams;
516  0 if (preset == null && arguments != null && arguments.size() > 0)
517  0 jobParams = new WsJobParameters(paramStore, null, arguments);
518    else
519  0 jobParams = new WsJobParameters(paramStore, preset, null);
520  0 if (preset != null)
521  0 jobParams.setName(MessageManager.getString(
522    "label.adjusting_parameters_for_calculation"));
523  0 var stage = jobParams.showRunDialog();
524  0 return stage.thenApply(startJob -> {
525  0 if (!startJob)
526  0 return null; // null if cancelled
527  0 if (jobParams.getPreset() != null)
528  0 return jobParams.getPreset().getArguments();
529  0 if (jobParams.isServiceDefaults())
530  0 return Collections.emptyList();
531    else
532  0 return jobParams.getJobParams();
533    });
534    }
535    }