JAL-3878 Add ws menu builder and attach to the align frame.
authorMateusz Warowny <mmzwarowny@dundee.ac.uk>
Thu, 18 Nov 2021 21:20:58 +0000 (22:20 +0100)
committerMateusz Warowny <mmzwarowny@dundee.ac.uk>
Thu, 18 Nov 2021 21:20:58 +0000 (22:20 +0100)
src/jalview/gui/AlignFrame.java
src/jalview/gui/Desktop.java
src/jalview/gui/SlivkaPreferences.java
src/jalview/ws2/WebServiceDiscovererI.java
src/jalview/ws2/gui/WebServicesMenuBuilder.java [new file with mode: 0644]

index 6f3a7e3..b872e80 100644 (file)
@@ -115,7 +115,11 @@ import jalview.ws.params.ArgumentI;
 import jalview.ws.params.ParamDatastoreI;
 import jalview.ws.params.WsParamSetI;
 import jalview.ws.seqfetcher.DbSourceProxy;
-import jalview.ws.slivkaws.SlivkaWSDiscoverer;
+import jalview.ws2.slivka.SlivkaWSDiscoverer;
+import jalview.ws2.WebServiceDiscovererI;
+import jalview.ws2.gui.WebServicesMenuBuilder;
+import jalview.ws2.operations.Operation;
+
 import java.io.IOException;
 import java.util.HashSet;
 import java.util.Set;
@@ -909,13 +913,19 @@ public class AlignFrame extends GAlignFrame
     buildWebServicesMenu();
   }
 
+  private void servicesChanged(WebServiceDiscovererI discoverer,
+      List<Operation> operations)
+  {
+    buildWebServicesMenu();
+  }
+
   /* Set up intrinsic listeners for dynamically generated GUI bits. */
   private void addServiceListeners()
   {
     if (Cache.getDefault("SHOW_SLIVKA_SERVICES", true))
     {
-      WSDiscovererI discoverer = SlivkaWSDiscoverer.getInstance();
-      discoverer.addServiceChangeListener(this);
+      WebServiceDiscovererI discoverer = SlivkaWSDiscoverer.getInstance();
+      discoverer.addOperationsChangeListener(this::servicesChanged);
     }
     if (Cache.getDefault("SHOW_JWS2_SERVICES", true))
     {
@@ -932,7 +942,7 @@ public class AlignFrame extends GAlignFrame
       @Override
       public void internalFrameClosed(InternalFrameEvent e) {
         System.out.println("deregistering discoverer listener");
-        SlivkaWSDiscoverer.getInstance().removeServiceChangeListener(AlignFrame.this);
+        SlivkaWSDiscoverer.getInstance().removeOperationsChangeListener(AlignFrame.this::servicesChanged);
         Jws2Discoverer.getInstance().removeServiceChangeListener(AlignFrame.this);
         Desktop.getInstance().removeJalviewPropertyChangeListener("services", legacyListener);
         closeMenuItem_actionPerformed(true);
@@ -4711,6 +4721,34 @@ public class AlignFrame extends GAlignFrame
     }
   }
 
+  private void buildWebServicesMenu(WebServiceDiscovererI discoverer, JMenu menu)
+  {
+    if (discoverer.hasServices())
+    {
+      var builder = new WebServicesMenuBuilder();
+      builder.addSelectedHostChangeListener((name, op) -> {
+        Cache.log.info("Rebuilding menu on host change");
+        menu.removeAll();
+        builder.buildMenu(menu, this);
+      });
+      builder.addAllOperations(discoverer.getOperations());
+      menu.removeAll();
+      builder.buildMenu(menu, this);
+    }
+    if (discoverer.isRunning())
+    {
+      JMenuItem item = new JMenuItem("Service discovery in progress.");
+      item.setEnabled(false);
+      menu.add(item);
+    }
+    else if (!discoverer.hasServices())
+    {
+      JMenuItem item = new JMenuItem("No services available.");
+      item.setEnabled(false);
+      menu.add(item);
+    }
+  }
+
   /**
    * construct any groupURL type service menu entries.
    * 
index 88c94f6..db9931b 100644 (file)
@@ -2736,7 +2736,7 @@ public class Desktop extends GDesktop
     }
     if (Cache.getDefault("SHOW_SLIVKA_SERVICES", true))
     {
-      tasks.add(jalview.ws.slivkaws.SlivkaWSDiscoverer.getInstance().startDiscoverer());
+      tasks.add(jalview.ws2.slivka.SlivkaWSDiscoverer.getInstance().startDiscoverer());
     }
     if (blocking)
     {
index 6c365b9..31ab5dd 100644 (file)
@@ -3,7 +3,8 @@ package jalview.gui;
 import jalview.bin.Cache;
 import jalview.util.MessageManager;
 import jalview.ws.WSDiscovererI;
-import jalview.ws.slivkaws.SlivkaWSDiscoverer;
+import jalview.ws2.slivka.SlivkaWSDiscoverer;
+import jalview.ws2.WebServiceDiscovererI;
 
 import java.awt.BorderLayout;
 import java.awt.Color;
@@ -42,7 +43,7 @@ public class SlivkaPreferences extends JPanel
     setPreferredSize(new Dimension(500, 450));
   }
 
-  WSDiscovererI discoverer;
+  WebServiceDiscovererI discoverer;
 
   private final ArrayList<String> urls = new ArrayList<>();
 
@@ -97,16 +98,16 @@ public class SlivkaPreferences extends JPanel
           hasFocus, row, column);
       switch ((Integer) value)
       {
-      case WSDiscovererI.STATUS_NO_SERVICES:
+      case WebServiceDiscovererI.STATUS_NO_SERVICES:
         setForeground(Color.ORANGE);
         break;
-      case WSDiscovererI.STATUS_OK:
+      case WebServiceDiscovererI.STATUS_OK:
         setForeground(Color.GREEN);
         break;
-      case WSDiscovererI.STATUS_INVALID:
+      case WebServiceDiscovererI.STATUS_INVALID:
         setForeground(Color.RED);
         break;
-      case WSDiscovererI.STATUS_UNKNOWN:
+      case WebServiceDiscovererI.STATUS_UNKNOWN:
       default:
         setForeground(Color.LIGHT_GRAY);
       }
@@ -141,7 +142,7 @@ public class SlivkaPreferences extends JPanel
   {
     String input = (String) JvOptionPane
         .showInternalInputDialog(
-            this, 
+            this,
             MessageManager.getString("label.url:"),
             UIManager.getString("OptionPane.inputDialogTitle", MessageManager.getLocale()),
             JOptionPane.QUESTION_MESSAGE,
@@ -173,9 +174,9 @@ public class SlivkaPreferences extends JPanel
     if (input != null)
     {
       urls.add(input);
-      statuses.add(discoverer.getServerStatusFor(input));
+      statuses.add(discoverer.getStatusForUrl(input));
       urlTableModel.fireTableRowsInserted(urls.size(), urls.size());
-      discoverer.setServiceUrls(urls);
+      discoverer.setUrls(urls);
     }
   };
 
@@ -187,9 +188,9 @@ public class SlivkaPreferences extends JPanel
       if (input != null)
       {
         urls.set(i, input);
-        statuses.set(i, discoverer.getServerStatusFor(input));
+        statuses.set(i, discoverer.getStatusForUrl(input));
         urlTableModel.fireTableRowsUpdated(i, i);
-        discoverer.setServiceUrls(urls);
+        discoverer.setUrls(urls);
       }
     }
   };
@@ -201,7 +202,7 @@ public class SlivkaPreferences extends JPanel
       urls.remove(i);
       statuses.remove(i);
       urlTableModel.fireTableRowsDeleted(i, i);
-      discoverer.setServiceUrls(urls);
+      discoverer.setUrls(urls);
     }
   };
 
@@ -210,7 +211,7 @@ public class SlivkaPreferences extends JPanel
     if (i > 0)
     {
       moveTableRow(i, i - 1);
-      discoverer.setServiceUrls(urls);
+      discoverer.setUrls(urls);
     }
   };
 
@@ -219,7 +220,7 @@ public class SlivkaPreferences extends JPanel
     if (i >= 0 && i < urls.size() - 1)
     {
       moveTableRow(i, i + 1);
-      discoverer.setServiceUrls(urls);
+      discoverer.setUrls(urls);
     }
   };
 
@@ -324,13 +325,13 @@ public class SlivkaPreferences extends JPanel
   };
 
   private ActionListener resetServicesAction = (ActionEvent e) -> {
-    discoverer.setServiceUrls(null);
+    discoverer.setUrls(null);
     urls.clear();
     statuses.clear();
-    urls.addAll(discoverer.getServiceUrls());
+    urls.addAll(discoverer.getUrls());
     for (String url : urls)
     {
-      statuses.add(discoverer.getServerStatusFor(url));
+      statuses.add(discoverer.getStatusForUrl(url));
     }
     urlTableModel.fireTableDataChanged();
   };
@@ -359,10 +360,10 @@ public class SlivkaPreferences extends JPanel
   {
     // Initial URLs loading
     discoverer = SlivkaWSDiscoverer.getInstance();
-    urls.addAll(discoverer.getServiceUrls());
+    urls.addAll(discoverer.getUrls());
     for (String url : urls)
     {
-      statuses.add(discoverer.getServerStatusFor(url));
+      statuses.add(discoverer.getStatusForUrl(url));
     }
   }
 }
index 0932a87..30cc22e 100644 (file)
@@ -120,7 +120,7 @@ public interface WebServiceDiscovererI
      * @param list
      */
     public void operationsChanged(WebServiceDiscovererI discoverer,
-        List<Operation> list);
+        List<Operation> operations);
   }
 
   List<OperationsChangeListener> serviceListeners = new CopyOnWriteArrayList<>();
@@ -143,7 +143,7 @@ public interface WebServiceDiscovererI
    * @param listener
    *          listener to be removed
    */
-  public default void removeServiceChangeListener(
+  public default void removeOperationsChangeListener(
       OperationsChangeListener listener)
   {
     serviceListeners.remove(listener);
diff --git a/src/jalview/ws2/gui/WebServicesMenuBuilder.java b/src/jalview/ws2/gui/WebServicesMenuBuilder.java
new file mode 100644 (file)
index 0000000..3a2f3c6
--- /dev/null
@@ -0,0 +1,208 @@
+package jalview.ws2.gui;
+
+import java.awt.Color;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.CopyOnWriteArrayList;
+
+import javax.swing.JMenu;
+import javax.swing.JMenuItem;
+
+import jalview.gui.AlignFrame;
+import jalview.gui.Desktop;
+import jalview.gui.JvSwingUtils;
+import jalview.util.MessageManager;
+import jalview.ws2.WebServiceI;
+import jalview.ws2.operations.Operation;
+
+/**
+ * 
+ * @author mmwarowny
+ *
+ */
+public class WebServicesMenuBuilder
+{
+  @FunctionalInterface
+  public static interface SelectedHostChangeListener
+  {
+    public void selectedHostChanged(String opName, Operation op);
+  }
+
+  List<Operation> operations = new ArrayList<>();
+
+  /**
+   * Mapping of service name to the preferred url this service is hosted at.
+   */
+  Map<String, String> selectedHost = new HashMap<>();
+
+  public void addOperation(Operation op)
+  {
+    operations.add(op);
+  }
+
+  public void addAllOperations(Collection<Operation> operations)
+  {
+    this.operations.addAll(operations);
+  }
+
+  public void removeOperation(Operation op)
+  {
+    operations.remove(op);
+  }
+
+  public void clearOperations()
+  {
+    operations.clear();
+  }
+
+  public void buildMenu(JMenu atMenu, AlignFrame frame)
+  {
+    Map<String, List<Operation>> oneshotOperations = new HashMap<>();
+    Map<String, List<Operation>> interactiveOperations = new HashMap<>();
+    for (Operation op : operations)
+    {
+      var mapping = op.isInteractive() ? interactiveOperations : oneshotOperations;
+      if (!mapping.containsKey(op.getTypeName()))
+        mapping.put(op.getTypeName(), new ArrayList<>());
+      mapping.get(op.getTypeName()).add(op);
+    }
+    var keysSet = new HashSet<>(oneshotOperations.keySet());
+    keysSet.addAll(interactiveOperations.keySet());
+    var keys = new ArrayList<>(keysSet);
+    keys.sort(Comparator.<String> naturalOrder());
+    for (String opType : keys)
+    {
+      var submenu = new JMenu(opType);
+      var oneshot = oneshotOperations.get(opType);
+      if (oneshot != null)
+        addOneshotOperations(oneshot, submenu, frame);
+      var interactive = interactiveOperations.get(opType);
+      if (interactive != null)
+      {
+        if (oneshot != null)
+          submenu.addSeparator();
+        addInteractiveOperations(interactive, submenu, frame);
+      }
+      atMenu.add(submenu);
+    }
+  }
+
+  private void addOneshotOperations(List<Operation> operations, JMenu submenu,
+      AlignFrame frame)
+  {
+    operations = new ArrayList<>(operations);
+    operations.sort(Comparator
+        .<Operation, String> comparing(o -> o.getHostName())
+        .<String> thenComparing(o -> o.getName()));
+    String lastHost = null;
+    for (final Operation op : operations)
+    {
+      String host = op.getHostName();
+      if (lastHost != host)
+      {
+        if (lastHost != null)
+          submenu.addSeparator();
+        var menuItem = new JMenuItem(host);
+        menuItem.setForeground(Color.blue);
+        menuItem.addActionListener(e -> Desktop.showUrl(host));
+        submenu.add(menuItem);
+        lastHost = host;
+      }
+      submenu.addSeparator();
+      op.getMenuBuilder().buildMenu(submenu, frame);
+    }
+  }
+
+  private void addInteractiveOperations(List<Operation> operations,
+      JMenu submenu, AlignFrame frame)
+  {
+    Map<String, List<Operation>> groupedOperations = new HashMap<>();
+    for (Operation op : operations)
+    {
+      if (!groupedOperations.containsKey(op.getName()))
+        groupedOperations.put(op.getName(), new ArrayList<>());
+      groupedOperations.get(op.getName()).add(op);
+    }
+    var keys = new ArrayList<>(groupedOperations.keySet());
+    keys.sort(Comparator.<String> naturalOrder());
+    for (String opName : keys)
+    {
+      var ops = groupedOperations.get(opName);
+      var selectedHost = getSelectedHost(opName);
+      Operation selectedOperation = null;
+      for (var op : ops)
+      {
+        if (op.getHostName().equals(selectedHost))
+        {
+          selectedOperation = op;
+          break;
+        }
+      }
+      if (selectedOperation == null || selectedHost == null)
+        selectedOperation = ops.get(0);
+      {
+        final var hostName = selectedOperation.getHostName();
+        var hostItem = new JMenuItem(hostName);
+        hostItem.setForeground(Color.blue);
+        hostItem.addActionListener(e -> Desktop.showUrl(hostName));
+        submenu.add(hostItem);
+      }
+      selectedOperation.getMenuBuilder().buildMenu(submenu, frame);
+      if (ops.size() > 1)
+      {
+        JMenu alternatesMenu = new JMenu(MessageManager.getString("label.switch_server"));
+        submenu.add(alternatesMenu);
+        alternatesMenu.setToolTipText(JvSwingUtils.wrapTooltip(false,
+            MessageManager.getString("label.choose_jabaws_server")));
+        for (final Operation op : ops)
+        {
+          if (op == selectedOperation)
+            continue;
+          var hostItem = new JMenuItem(op.getHostName());
+          hostItem.setForeground(Color.blue);
+          hostItem.addActionListener(e -> {
+            setSelectedHost(op.getName(), op.getHostName());
+            fireSelectedHostChanged(op.getName(), op);
+          });
+          alternatesMenu.add(hostItem);
+        }
+      }
+    }
+  }
+
+  public String getSelectedHost(String serviceName)
+  {
+    return selectedHost.getOrDefault(serviceName, null);
+  }
+
+  public void setSelectedHost(WebServiceI service)
+  {
+    setSelectedHost(service.getName(), service.getHostName());
+  }
+
+  public void setSelectedHost(String serviceName, String hostName)
+  {
+    selectedHost.put(serviceName, hostName);
+  }
+
+  List<SelectedHostChangeListener> hostChangeListeners = new CopyOnWriteArrayList<>();
+
+  public void addSelectedHostChangeListener(SelectedHostChangeListener l)
+  {
+    hostChangeListeners.add(l);
+  }
+
+  private void fireSelectedHostChanged(String opName, Operation op)
+  {
+    for (var listener : hostChangeListeners)
+    {
+      listener.selectedHostChanged(opName, op);
+    }
+  }
+
+}