javajs.async.AsyncSwingWorker for SequenceFetcher
authorBobHanson <hansonr@stolaf.edu>
Fri, 29 May 2020 02:53:08 +0000 (21:53 -0500)
committerBobHanson <hansonr@stolaf.edu>
Fri, 29 May 2020 02:53:08 +0000 (21:53 -0500)
- set to synchronous mode for Java due to blocking on Ensembl.

- Hmm. Could be adapted to not use the Event Queue.

src/jalview/fts/core/GFTSPanel.java
src/jalview/fts/service/pdb/PDBFTSPanel.java
src/jalview/fts/service/uniprot/UniprotFTSPanel.java
src/jalview/gui/SequenceFetcher.java
test/jalview/io/CrossRef2xmlTests.java

index 7ff0f75..e9fd33e 100644 (file)
@@ -916,9 +916,7 @@ public abstract class GFTSPanel extends JPanel implements GFTSPanelI
 
   public void transferToSequenceFetcher(String ids)
   {
-    seqFetcher.setQuery(ids);
-    Thread worker = new Thread(seqFetcher);
-    worker.start();
+    seqFetcher.fetch(ids, true);
   }
 
   @Override
index 2dd0f8c..7a2fe83 100644 (file)
@@ -222,9 +222,7 @@ public class PDBFTSPanel extends GFTSPanel
     }
 
     String ids = selectedIds.toString();
-    seqFetcher.setQuery(ids);
-    Thread worker = new Thread(seqFetcher);
-    worker.start();
+    seqFetcher.fetch(ids, true);
     delayAndEnableActionButtons();
   }
 
index 182e29b..2d7894f 100644 (file)
@@ -221,9 +221,7 @@ public class UniprotFTSPanel extends GFTSPanel
     }
 
     String ids = selectedIds.toString();
-    seqFetcher.setQuery(ids);
-    Thread worker = new Thread(seqFetcher);
-    worker.start();
+    seqFetcher.fetch(ids, true);
     delayAndEnableActionButtons();
   }
 
index b8e3b33..549d198 100755 (executable)
@@ -36,6 +36,7 @@ import jalview.util.Platform;
 import jalview.ws.seqfetcher.DbSourceProxy;
 
 import java.awt.BorderLayout;
+import java.awt.Component;
 import java.awt.Font;
 import java.awt.event.ActionEvent;
 import java.awt.event.ActionListener;
@@ -57,6 +58,8 @@ import javax.swing.JScrollPane;
 import javax.swing.JTextArea;
 import javax.swing.SwingConstants;
 
+import javajs.async.AsyncSwingWorker;
+
 /**
  * A panel where the use may choose a database source, and enter one or more
  * accessions, to retrieve entries from the database.
@@ -65,7 +68,7 @@ import javax.swing.SwingConstants;
  * instead to perform the search and selection.
  */
 @SuppressWarnings("serial")
-public class SequenceFetcher extends JPanel implements Runnable
+public class SequenceFetcher extends JPanel
 {
 
   JLabel exampleAccession;
@@ -131,8 +134,8 @@ public class SequenceFetcher extends JPanel implements Runnable
 
     frame = new JInternalFrame();
     frame.setContentPane(this);
-    Desktop.addInternalFrame(frame, getFrameTitle(), true, 400, 
-               Platform.isAMacAndNotJS() ? 240 : 180);
+    Desktop.addInternalFrame(frame, getFrameTitle(), true, 400,
+            Platform.isAMacAndNotJS() ? 240 : 180);
   }
 
   private String getFrameTitle()
@@ -412,9 +415,9 @@ public class SequenceFetcher extends JPanel implements Runnable
       text = text.replace(",", ";");
     }
     text = text.replaceAll("(\\s|[; ])+", ";");
-    if (!t0.equals(text)) 
+    if (!t0.equals(text))
     {
-         textArea.setText(text);
+      textArea.setText(text);
     }
     if (text.isEmpty())
     {
@@ -429,12 +432,30 @@ public class SequenceFetcher extends JPanel implements Runnable
     okBtn.setEnabled(false);
     closeBtn.setEnabled(false);
     backBtn.setEnabled(false);
+    fetch(null, true);
+  }
 
-    Thread worker = new Thread(this);
-    worker.start();
+  public void fetch(String ids, boolean isAsync)
+  {
+    isAsync &= Platform.isJS();
+    if (ids == null)
+    {
+      ids = textArea.getText();
+    }
+    else
+    {
+      textArea.setText(ids);
+    }
+    Component parent = null; // or this
+    String title = null; // or some title for the progress monitor
+    int min = AsyncFetchTask.STATE_INIT;
+    int max = AsyncFetchTask.STATE_DONE;
+    int msDelay = (isAsync ? 5 : 0);
+    new AsyncFetchTask(ids, parent, title, msDelay, min, max).execute();
   }
 
-  private void resetDialog()
+
+  protected void resetDialog()
   {
     exampleBtn.setEnabled(true);
     textArea.setEnabled(true);
@@ -443,176 +464,275 @@ public class SequenceFetcher extends JPanel implements Runnable
     backBtn.setEnabled(parentSearchPanel != null);
   }
 
-  @Override
-  public void run()
+  /**
+   * This asynchronous class allows for a single-threaded state machine
+   * SwingWorker to process a list of requests from multiple sources.
+   * 
+   * A standard ProcessMonitor could be attached to this task, but it is
+   * currently not.
+   * 
+   * @author hansonr
+   *
+   */
+  private class AsyncFetchTask extends AsyncSwingWorker
   {
-    boolean addToLast = false;
-    List<String> aresultq = new ArrayList<>();
-    List<String> presultTitle = new ArrayList<>();
-    List<AlignmentI> presult = new ArrayList<>();
-    List<AlignmentI> aresult = new ArrayList<>();
-    List<DbSourceProxy> sources = jalview.ws.SequenceFetcher.getInstance()
-            .getSourceProxy((String) database.getSelectedItem());
-    Iterator<DbSourceProxy> proxies = sources.iterator();
-    String[] qries = textArea.getText().trim().split(";");
-    List<String> nextFetch = Arrays.asList(qries);
-    Iterator<String> en = Arrays.asList(new String[0]).iterator();
-    int nqueries = qries.length;
-
-    FeatureSettingsModelI preferredFeatureColours = null;
-    while (proxies.hasNext() && (en.hasNext() || nextFetch.size() > 0))
-    {
-      if (!en.hasNext() && nextFetch.size() > 0)
-      {
-        en = nextFetch.iterator();
-        nqueries = nextFetch.size();
-        // save the remaining queries in the original array
-        qries = nextFetch.toArray(new String[nqueries]);
-        nextFetch = new ArrayList<>();
-      }
 
-      DbSourceProxy proxy = proxies.next();
-      try
+    private boolean addToLast = false;
+
+    private List<String> aresultq = new ArrayList<>();
+
+    private List<String> presultTitle = new ArrayList<>();
+
+    private List<AlignmentI> presult = new ArrayList<>();
+
+    private List<AlignmentI> aresult = new ArrayList<>();
+
+    private FeatureSettingsModelI preferredFeatureColours = null;
+
+    private List<DbSourceProxy> sources;
+
+    private Iterator<DbSourceProxy> sourceIterator;
+
+    private String[] fetchArray;
+
+    private List<String> fetchList;
+
+    private Iterator<String> fetchIterator;
+
+    private int fetchCount;
+
+    private DbSourceProxy source;
+
+    private String ids;
+
+    public AsyncFetchTask(String ids, Component owner, String title,
+            int delayMillis,
+            int min, int max)
+    {
+      super(owner, title, delayMillis, min, max);
+      this.ids = ids;
+    }
+
+    @Override
+    public void initAsync()
+    {
+      sources = jalview.ws.SequenceFetcher.getInstance()
+              .getSourceProxy((String) database.getSelectedItem());
+      sourceIterator = sources.iterator();
+      fetchArray = ids.trim().split(";");
+      fetchList = Arrays.asList(fetchArray);
+    }
+
+    private final static int STATE_INIT = 0;
+
+    private final static int STATE_NEXT_SOURCE = 10;
+
+    private final static int STATE_FETCH_SINGLE = 30;
+
+    private final static int STATE_FETCH_MULTIPLE = 40;
+
+    private final static int STATE_PROCESS = 50;
+
+    private final static int STATE_PARSE_RESULTS = 80;
+
+    private final static int STATE_DONE = 100;
+
+    @Override
+    public int doInBackgroundAsync(int progress)
+    {
+      switch (progress)
       {
-        // update status
-        guiWindow.setProgressBar(MessageManager.formatMessage(
+      case STATE_INIT:
+      case STATE_NEXT_SOURCE:
+        boolean doneFetching = (fetchIterator == null
+                || !fetchIterator.hasNext());
+        boolean havePending = (fetchList.size() > 0);
+        if (!sourceIterator.hasNext() || doneFetching && !havePending)
+        {
+          showProgress((presult.size() > 0)
+                  ? MessageManager.getString("status.parsing_results")
+                  : MessageManager.getString("status.processing"));
+          return STATE_PARSE_RESULTS;
+        }
+        source = sourceIterator.next();
+        if (doneFetching)
+        {
+          // if we are here, we must have some pending still
+          fetchCount = fetchList.size();
+          fetchIterator = fetchList.iterator();
+          // save the remaining queries in the original array
+          fetchArray = fetchList.toArray(new String[fetchCount]);
+          // and clear the
+          fetchList = new ArrayList<>();
+        }
+        showProgress(MessageManager.formatMessage(
                 "status.fetching_sequence_queries_from", new String[]
-                { Integer.valueOf(nqueries).toString(),
-                    proxy.getDbName() }),
-                Thread.currentThread().hashCode());
-        if (proxy.getMaximumQueryCount() == 1)
+                { Integer.valueOf(fetchCount).toString(),
+                    source.getDbName() }));
+        return (source.getMaximumQueryCount() == 1 ? STATE_FETCH_SINGLE
+                : STATE_FETCH_MULTIPLE);
+      case STATE_FETCH_SINGLE:
+        if (fetchIterator.hasNext())
         {
-          /*
-           * proxy only handles one accession id at a time
-           */
-          while (en.hasNext())
+          // source only handles one accession id at a time
+          try
+          {
+            if (delayMillis == 0)
+            {
+              // for CrossRef2xmlTest only
+              Thread.sleep(5);
+            }
+            String accession = fetchIterator.next();
+            if (!fetchSingleAccession(source, accession, aresultq, aresult))
+            {
+              fetchList.add(accession);
+            }
+          } catch (Throwable e)
           {
-            String acc = en.next();
-            if (!fetchSingleAccession(proxy, acc, aresultq, aresult))
+            if (!showError(e))
             {
-              nextFetch.add(acc);
+              return STATE_DONE;
             }
           }
+          return STATE_FETCH_SINGLE;
         }
-        else
-        {
-          /*
-           * proxy can fetch multiple accessions at one time
-           */
-          fetchMultipleAccessions(proxy, en, aresultq, aresult, nextFetch);
-        }
-      } catch (Exception e)
-      {
-        showErrorMessage("Error retrieving " + textArea.getText() + " from "
-                + database.getSelectedItem());
-        // error
-        // +="Couldn't retrieve sequences from "+database.getSelectedItem();
-        System.err.println("Retrieval failed for source ='"
-                + database.getSelectedItem() + "' and query\n'"
-                + textArea.getText() + "'\n");
-        e.printStackTrace();
-      } catch (OutOfMemoryError e)
-      {
-        showErrorMessage("Out of Memory when retrieving "
-                + textArea.getText() + " from " + database.getSelectedItem()
-                + "\nPlease see the Jalview FAQ for instructions for increasing the memory available to Jalview.\n");
-        e.printStackTrace();
-      } catch (Error e)
-      {
-        showErrorMessage("Serious Error retrieving " + textArea.getText()
-                + " from " + database.getSelectedItem());
-        e.printStackTrace();
-      }
-
-      // Stack results ready for opening in alignment windows
-      if (aresult != null && aresult.size() > 0)
-      {
-        FeatureSettingsModelI proxyColourScheme = proxy
-                .getFeatureColourScheme();
-        if (proxyColourScheme != null)
+        return STATE_PROCESS;
+      case STATE_FETCH_MULTIPLE:
+        // proxy can fetch multiple accessions at one time
+        try
         {
-          preferredFeatureColours = proxyColourScheme;
-        }
-
-        AlignmentI ar = null;
-        if (proxy.isAlignmentSource())
+          fetchMultipleAccessions(source, fetchIterator, aresultq, aresult,
+                  fetchList);
+        } catch (Throwable e)
         {
-          addToLast = false;
-          // new window for each result
-          while (aresult.size() > 0)
+          if (!showError(e))
           {
-            presult.add(aresult.remove(0));
-            presultTitle.add(
-                    aresultq.remove(0) + " " + getDefaultRetrievalTitle());
+            return STATE_DONE;
           }
         }
-        else
+        return STATE_PROCESS;
+      case STATE_PROCESS:
+        // Stack results ready for opening in alignment windows
+        if (aresult != null && aresult.size() > 0)
         {
-          String titl = null;
-          if (addToLast && presult.size() > 0)
+          FeatureSettingsModelI proxyColourScheme = source
+                  .getFeatureColourScheme();
+          if (proxyColourScheme != null)
+          {
+            preferredFeatureColours = proxyColourScheme;
+          }
+
+          AlignmentI ar = null;
+          if (source.isAlignmentSource())
           {
-            ar = presult.remove(presult.size() - 1);
-            titl = presultTitle.remove(presultTitle.size() - 1);
+            addToLast = false;
+            // new window for each result
+            while (aresult.size() > 0)
+            {
+              presult.add(aresult.remove(0));
+              presultTitle.add(aresultq.remove(0) + " "
+                      + getDefaultRetrievalTitle());
+            }
           }
-          // concatenate all results in one window
-          while (aresult.size() > 0)
+          else
           {
-            if (ar == null)
+            String titl = null;
+            if (addToLast && presult.size() > 0)
             {
-              ar = aresult.remove(0);
+              ar = presult.remove(presult.size() - 1);
+              titl = presultTitle.remove(presultTitle.size() - 1);
             }
-            else
+            // concatenate all results in one window
+            while (aresult.size() > 0)
             {
-              ar.append(aresult.remove(0));
+              if (ar == null)
+              {
+                ar = aresult.remove(0);
+              }
+              else
+              {
+                ar.append(aresult.remove(0));
+              }
             }
+            addToLast = true;
+            presult.add(ar);
+            presultTitle.add(titl);
           }
-          addToLast = true;
-          presult.add(ar);
-          presultTitle.add(titl);
         }
+        showProgress(MessageManager.getString("status.finshed_querying"));
+        return STATE_NEXT_SOURCE;
+      case STATE_PARSE_RESULTS:
+        while (presult.size() > 0)
+        {
+          parseResult(presult.remove(0), presultTitle.remove(0), null,
+                  preferredFeatureColours);
+        }
+        break;
       }
-      guiWindow.setProgressBar(
-              MessageManager.getString("status.finshed_querying"),
-              Thread.currentThread().hashCode());
+      return STATE_DONE;
     }
-    guiWindow
-            .setProgressBar(
-                    (presult.size() > 0)
-                            ? MessageManager
-                                    .getString("status.parsing_results")
-                            : MessageManager.getString("status.processing"),
-                    Thread.currentThread().hashCode());
-    // process results
-    while (presult.size() > 0)
-    {
-      parseResult(presult.remove(0), presultTitle.remove(0), null,
-              preferredFeatureColours);
+
+    private void showProgress(String msg)
+    {
+      guiWindow.setProgressBar(msg, Thread.currentThread().hashCode());
     }
-    // only remove visual delay after we finished parsing.
-    guiWindow.setProgressBar(null, Thread.currentThread().hashCode());
-    if (nextFetch.size() > 0)
-    {
-      StringBuffer sb = new StringBuffer();
-      sb.append("Didn't retrieve the following "
-              + (nextFetch.size() == 1 ? "query"
-                      : nextFetch.size() + " queries")
-              + ": \n");
-      int l = sb.length(), lr = 0;
-      for (String s : nextFetch)
+
+    @Override
+    public void doneAsync()
+    {
+      showProgress(null);
+      if (fetchList.size() > 0)
       {
-        if (l != sb.length())
-        {
-          sb.append("; ");
-        }
-        if (lr - sb.length() > 40)
+        StringBuffer sb = new StringBuffer();
+        sb.append("Didn't retrieve the following "
+                + (fetchList.size() == 1 ? "query"
+                        : fetchList.size() + " queries")
+                + ": \n");
+        int l = sb.length(), lr = 0;
+        for (String s : fetchList)
         {
-          sb.append("\n");
+          if (l != sb.length())
+          {
+            sb.append("; ");
+          }
+          if (lr - sb.length() > 40)
+          {
+            sb.append("\n");
+          }
+          sb.append(s);
         }
-        sb.append(s);
+        showErrorMessage(sb.toString());
       }
-      showErrorMessage(sb.toString());
+      resetDialog();
     }
-    resetDialog();
+
+    private boolean showError(Throwable e)
+    {
+      String problem = "retrieving " + ids + " from "
+              + database.getSelectedItem();
+      if (e instanceof Exception)
+      {
+        showErrorMessage("Error " + problem);
+        System.err.println("Retrieval failed for source ='"
+                + database.getSelectedItem() + "' and query\n'"
+                + ids + "'\n");
+      }
+      else if (e instanceof OutOfMemoryError)
+      {
+        showErrorMessage("Out of Memory when " + problem
+                + "\nPlease see the Jalview FAQ for instructions for increasing the memory available to Jalview.\n");
+        // option here to return false and quit this, but that is not how
+        // original code works - BH
+        // return false;
+      }
+      else
+      {
+        showErrorMessage("Serious Error " + problem);
+      }
+      e.printStackTrace();
+      return true;
+    }
+
   }
 
   /**
@@ -688,18 +808,19 @@ public class SequenceFetcher extends JPanel implements Runnable
     boolean success = false;
     try
     {
-      if (aresult != null)
-      {
-        try
-        {
-          // give the server a chance to breathe
-          Thread.sleep(5);
-        } catch (Exception e)
-        {
-          //
-        }
-      }
-
+      // BH no longer necessary; we are doing 5-ms asynchronous delays all along
+      // if (aresult != null)
+      // {
+      // try
+      // {
+      // // give the server a chance to breathe
+      // Thread.sleep(5);
+      // } catch (Exception e)
+      // {
+      // //
+      // }
+      // }
+      //
       AlignmentI indres = null;
       try
       {
@@ -741,12 +862,12 @@ public class SequenceFetcher extends JPanel implements Runnable
 
     for (String q : queries)
     {
-       // BH 2019.01.25 dbr is never used.
-//      DBRefEntry dbr = new DBRefEntry();
-//      dbr.setSource(proxy.getDbSource());
-//      dbr.setVersion(null);
+      // BH 2019.01.25 dbr is never used.
+      // DBRefEntry dbr = new DBRefEntry();
+      // dbr.setSource(proxy.getDbSource());
+      // dbr.setVersion(null);
       String accId = proxy.getAccessionIdFromQuery(q);
-//      dbr.setAccessionId(accId);
+      // dbr.setAccessionId(accId);
       boolean rfound = false;
       for (int r = 0, nr = rs.length; r < nr; r++)
       {
@@ -849,7 +970,8 @@ public class SequenceFetcher extends JPanel implements Runnable
       @Override
       public void run()
       {
-        JvOptionPane.showInternalMessageDialog(Desktop.getDesktopPane(), error,
+        JvOptionPane.showInternalMessageDialog(Desktop.getDesktopPane(),
+                error,
                 MessageManager.getString("label.error_retrieving_data"),
                 JvOptionPane.WARNING_MESSAGE);
       }
@@ -875,11 +997,6 @@ public class SequenceFetcher extends JPanel implements Runnable
     frame.setVisible(false);
   }
 
-  public void setQuery(String ids)
-  {
-    textArea.setText(ids);
-  }
-
   /**
    * Called to modify the search panel for embedding as an alternative tab of a
    * free text search panel. The database choice list is hidden (since the
@@ -894,4 +1011,5 @@ public class SequenceFetcher extends JPanel implements Runnable
     backBtn.setVisible(true);
     parentSearchPanel = parentPanel;
   }
+
 }
index daadfa5..d1c856b 100644 (file)
@@ -137,7 +137,7 @@ public class CrossRef2xmlTests extends Jalview2xmlBase
 
         SequenceFetcher sf = new SequenceFetcher(Desktop.getInstance(),
                 forSource, forAccession);
-        sf.run();
+        sf.fetch(null, false);
         AlignFrame[] afs = Desktop.getAlignFrames();
         if (afs.length == 0)
         {