From a33de5927ab7f5535263bb6d1b965c64d703f055 Mon Sep 17 00:00:00 2001 From: BobHanson Date: Thu, 28 May 2020 21:53:08 -0500 Subject: [PATCH] javajs.async.AsyncSwingWorker for SequenceFetcher - 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 | 4 +- src/jalview/fts/service/pdb/PDBFTSPanel.java | 4 +- .../fts/service/uniprot/UniprotFTSPanel.java | 4 +- src/jalview/gui/SequenceFetcher.java | 454 ++++++++++++-------- test/jalview/io/CrossRef2xmlTests.java | 2 +- 5 files changed, 290 insertions(+), 178 deletions(-) diff --git a/src/jalview/fts/core/GFTSPanel.java b/src/jalview/fts/core/GFTSPanel.java index 7ff0f75..e9fd33e 100644 --- a/src/jalview/fts/core/GFTSPanel.java +++ b/src/jalview/fts/core/GFTSPanel.java @@ -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 diff --git a/src/jalview/fts/service/pdb/PDBFTSPanel.java b/src/jalview/fts/service/pdb/PDBFTSPanel.java index 2dd0f8c..7a2fe83 100644 --- a/src/jalview/fts/service/pdb/PDBFTSPanel.java +++ b/src/jalview/fts/service/pdb/PDBFTSPanel.java @@ -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(); } diff --git a/src/jalview/fts/service/uniprot/UniprotFTSPanel.java b/src/jalview/fts/service/uniprot/UniprotFTSPanel.java index 182e29b..2d7894f 100644 --- a/src/jalview/fts/service/uniprot/UniprotFTSPanel.java +++ b/src/jalview/fts/service/uniprot/UniprotFTSPanel.java @@ -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(); } diff --git a/src/jalview/gui/SequenceFetcher.java b/src/jalview/gui/SequenceFetcher.java index b8e3b33..549d198 100755 --- a/src/jalview/gui/SequenceFetcher.java +++ b/src/jalview/gui/SequenceFetcher.java @@ -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 aresultq = new ArrayList<>(); - List presultTitle = new ArrayList<>(); - List presult = new ArrayList<>(); - List aresult = new ArrayList<>(); - List sources = jalview.ws.SequenceFetcher.getInstance() - .getSourceProxy((String) database.getSelectedItem()); - Iterator proxies = sources.iterator(); - String[] qries = textArea.getText().trim().split(";"); - List nextFetch = Arrays.asList(qries); - Iterator 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 aresultq = new ArrayList<>(); + + private List presultTitle = new ArrayList<>(); + + private List presult = new ArrayList<>(); + + private List aresult = new ArrayList<>(); + + private FeatureSettingsModelI preferredFeatureColours = null; + + private List sources; + + private Iterator sourceIterator; + + private String[] fetchArray; + + private List fetchList; + + private Iterator 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; } + } diff --git a/test/jalview/io/CrossRef2xmlTests.java b/test/jalview/io/CrossRef2xmlTests.java index daadfa5..d1c856b 100644 --- a/test/jalview/io/CrossRef2xmlTests.java +++ b/test/jalview/io/CrossRef2xmlTests.java @@ -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) { -- 1.7.10.2