2 * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
3 * Copyright (C) $$Year-Rel$$ The Jalview Authors
5 * This file is part of Jalview.
7 * Jalview is free software: you can redistribute it and/or
8 * modify it under the terms of the GNU General Public License
9 * as published by the Free Software Foundation, either version 3
10 * of the License, or (at your option) any later version.
12 * Jalview is distributed in the hope that it will be useful, but
13 * WITHOUT ANY WARRANTY; without even the implied warranty
14 * of MERCHANTABILITY or FITNESS FOR A PARTICULAR
15 * PURPOSE. See the GNU General Public License for more details.
17 * You should have received a copy of the GNU General Public License
18 * along with Jalview. If not, see <http://www.gnu.org/licenses/>.
19 * The Jalview Authors are detailed in the 'AUTHORS' file.
23 import jalview.api.FeatureSettingsModelI;
24 import jalview.bin.Cache;
25 import jalview.datamodel.AlignmentI;
26 import jalview.datamodel.DBRefEntry;
27 import jalview.datamodel.SequenceI;
28 import jalview.fts.core.GFTSPanel;
29 import jalview.fts.service.pdb.PDBFTSPanel;
30 import jalview.fts.service.uniprot.UniprotFTSPanel;
31 import jalview.io.FileFormatI;
32 import jalview.io.gff.SequenceOntologyI;
33 import jalview.util.DBRefUtils;
34 import jalview.util.MessageManager;
35 import jalview.util.Platform;
36 import jalview.ws.seqfetcher.DbSourceProxy;
38 import java.awt.BorderLayout;
39 import java.awt.Component;
41 import java.awt.event.ActionEvent;
42 import java.awt.event.ActionListener;
43 import java.awt.event.KeyAdapter;
44 import java.awt.event.KeyEvent;
45 import java.util.ArrayList;
46 import java.util.Arrays;
47 import java.util.HashSet;
48 import java.util.Iterator;
49 import java.util.List;
51 import javax.swing.JButton;
52 import javax.swing.JCheckBox;
53 import javax.swing.JComboBox;
54 import javax.swing.JInternalFrame;
55 import javax.swing.JLabel;
56 import javax.swing.JPanel;
57 import javax.swing.JScrollPane;
58 import javax.swing.JTextArea;
59 import javax.swing.SwingConstants;
61 import javajs.async.AsyncSwingWorker;
64 * A panel where the use may choose a database source, and enter one or more
65 * accessions, to retrieve entries from the database.
67 * If the selected source is Uniprot or PDB, a free text search panel is opened
68 * instead to perform the search and selection.
70 @SuppressWarnings("serial")
71 public class SequenceFetcher extends JPanel
74 JLabel exampleAccession;
76 JComboBox<String> database;
78 JCheckBox replacePunctuation;
92 IProgressIndicator guiWindow;
94 AlignFrame alignFrame;
96 GFTSPanel parentSearchPanel;
98 IProgressIndicator progressIndicator;
100 volatile boolean _isConstructing = false;
103 * Constructor given a client to receive any status or progress messages
104 * (currently either the Desktop, or an AlignFrame panel)
108 public SequenceFetcher(IProgressIndicator guiIndic)
110 this(guiIndic, null, null);
114 * Constructor with specified database and accession(s) to retrieve
120 public SequenceFetcher(IProgressIndicator guiIndic,
121 final String selectedDb, final String queryString)
123 this.progressIndicator = guiIndic;
125 this.guiWindow = progressIndicator;
127 if (progressIndicator instanceof AlignFrame)
129 alignFrame = (AlignFrame) progressIndicator;
133 textArea.setText(queryString);
135 frame = new JInternalFrame();
136 frame.setContentPane(this);
137 Desktop.addInternalFrame(frame, getFrameTitle(), true, 400,
138 Platform.isAMacAndNotJS() ? 240 : 180);
141 private String getFrameTitle()
143 return ((alignFrame == null)
144 ? MessageManager.getString("label.new_sequence_fetcher")
146 .getString("label.additional_sequence_fetcher"));
149 private void jbInit(String selectedDb)
151 this.setLayout(new BorderLayout());
153 database = new JComboBox<>();
154 database.setFont(JvSwingUtils.getLabelFont());
155 database.setPrototypeDisplayValue("ENSEMBLGENOMES ");
156 String[] sources = jalview.ws.SequenceFetcher.getInstance()
158 Arrays.sort(sources, String.CASE_INSENSITIVE_ORDER);
159 database.addItem(MessageManager.getString("action.select_ddbb"));
160 for (String source : sources)
162 database.addItem(source);
164 database.setSelectedItem(selectedDb);
165 if (database.getSelectedIndex() == -1)
167 database.setSelectedIndex(0);
169 database.setMaximumRowCount(database.getItemCount());
170 database.addActionListener(new ActionListener()
173 public void actionPerformed(ActionEvent e)
175 String currentSelection = (String) database.getSelectedItem();
176 updateExampleQuery(currentSelection);
178 if ("pdb".equalsIgnoreCase(currentSelection))
181 new PDBFTSPanel(SequenceFetcher.this);
183 else if ("uniprot".equalsIgnoreCase(currentSelection))
186 new UniprotFTSPanel(SequenceFetcher.this);
195 exampleAccession = new JLabel("");
196 exampleAccession.setFont(new Font("Verdana", Font.BOLD, 11));
197 JLabel jLabel1 = new JLabel(MessageManager
198 .getString("label.separate_multiple_accession_ids"));
199 jLabel1.setFont(new Font("Verdana", Font.ITALIC, 11));
200 jLabel1.setHorizontalAlignment(SwingConstants.LEFT);
202 replacePunctuation = new JCheckBox(
203 MessageManager.getString("label.replace_commas_semicolons"));
204 replacePunctuation.setHorizontalAlignment(SwingConstants.LEFT);
205 replacePunctuation.setFont(new Font("Verdana", Font.ITALIC, 11));
206 okBtn = new JButton(MessageManager.getString("action.ok"));
207 okBtn.addActionListener(new ActionListener()
210 public void actionPerformed(ActionEvent e)
212 ok_actionPerformed();
215 JButton clear = new JButton(MessageManager.getString("action.clear"));
216 clear.addActionListener(new ActionListener()
219 public void actionPerformed(ActionEvent e)
221 clear_actionPerformed();
225 exampleBtn = new JButton(MessageManager.getString("label.example"));
226 exampleBtn.addActionListener(new ActionListener()
229 public void actionPerformed(ActionEvent e)
231 example_actionPerformed();
234 closeBtn = new JButton(MessageManager.getString("action.cancel"));
235 closeBtn.addActionListener(new ActionListener()
238 public void actionPerformed(ActionEvent e)
240 close_actionPerformed(e);
243 backBtn = new JButton(MessageManager.getString("action.back"));
244 backBtn.addActionListener(new ActionListener()
247 public void actionPerformed(ActionEvent e)
249 parentSearchPanel.btn_back_ActionPerformed();
252 // back not visible unless embedded
253 backBtn.setVisible(false);
255 textArea = new JTextArea();
256 textArea.setFont(JvSwingUtils.getLabelFont());
257 textArea.setLineWrap(true);
258 textArea.addKeyListener(new KeyAdapter()
261 public void keyPressed(KeyEvent e)
263 if (e.getKeyCode() == KeyEvent.VK_ENTER)
265 ok_actionPerformed();
270 JPanel actionPanel = new JPanel();
271 actionPanel.add(backBtn);
272 actionPanel.add(exampleBtn);
273 actionPanel.add(clear);
274 actionPanel.add(okBtn);
275 actionPanel.add(closeBtn);
277 JPanel databasePanel = new JPanel();
278 databasePanel.setLayout(new BorderLayout());
279 databasePanel.add(database, BorderLayout.NORTH);
280 databasePanel.add(exampleAccession, BorderLayout.CENTER);
281 JPanel jPanel2a = new JPanel(new BorderLayout());
282 jPanel2a.add(jLabel1, BorderLayout.NORTH);
283 jPanel2a.add(replacePunctuation, BorderLayout.SOUTH);
284 databasePanel.add(jPanel2a, BorderLayout.SOUTH);
286 JPanel idsPanel = new JPanel();
287 idsPanel.setLayout(new BorderLayout(0, 5));
288 JScrollPane jScrollPane1 = new JScrollPane();
289 jScrollPane1.getViewport().add(textArea);
290 idsPanel.add(jScrollPane1, BorderLayout.CENTER);
292 this.add(actionPanel, BorderLayout.SOUTH);
293 this.add(idsPanel, BorderLayout.CENTER);
294 this.add(databasePanel, BorderLayout.NORTH);
298 * Answers a semi-colon-delimited string with the example query or queries for
299 * the selected database
304 protected String getExampleQueries(String db)
306 StringBuilder sb = new StringBuilder();
307 HashSet<String> hs = new HashSet<>();
308 for (DbSourceProxy dbs : jalview.ws.SequenceFetcher.getInstance()
311 String tq = dbs.getTestQuery();
312 if (hs.add(tq)) // not a duplicate source
321 return sb.toString();
325 * Action on selecting a database other than Uniprot or PDB is to enable or
326 * disable 'Replace commas', and await input in the query field
328 protected void otherSourceAction()
332 String eq = exampleAccession.getText();
333 // TODO this should be a property of the SequenceFetcher whether commas
334 // are allowed in the IDs...
336 boolean enablePunct = !(eq != null && eq.indexOf(",") > -1);
337 replacePunctuation.setEnabled(enablePunct);
339 } catch (Exception ex)
341 exampleAccession.setText("");
342 replacePunctuation.setEnabled(true);
348 * Sets the text of the example query to incorporate the example accession
349 * provided by the selected database source
351 * @param selectedDatabase
354 protected String updateExampleQuery(String selectedDatabase)
356 String eq = getExampleQueries(selectedDatabase);
357 exampleAccession.setText(MessageManager
358 .formatMessage("label.example_query_param", new String[]
364 * Action on clicking the 'Example' button is to write the example accession
365 * as the query text field value
367 protected void example_actionPerformed()
369 String eq = getExampleQueries((String) database.getSelectedItem());
370 textArea.setText(eq);
375 * Clears the query input field
377 protected void clear_actionPerformed()
379 textArea.setText("");
384 * Action on Close button is to close this frame, and also (if it is embedded
385 * in a search panel) to close the search panel
389 protected void close_actionPerformed(ActionEvent e)
393 frame.setClosed(true);
394 if (parentSearchPanel != null)
396 parentSearchPanel.btn_cancel_ActionPerformed();
398 } catch (Exception ex)
404 * Action on OK is to start the fetch for entered accession(s)
406 public void ok_actionPerformed()
409 * tidy inputs and check there is something to search for
411 String t0 = textArea.getText();
412 String text = t0.trim();
413 if (replacePunctuation.isEnabled() && replacePunctuation.isSelected())
415 text = text.replace(",", ";");
417 text = text.replaceAll("(\\s|[; ])+", ";");
418 if (!t0.equals(text))
420 textArea.setText(text);
426 "Please enter a (semi-colon separated list of) database id(s)");
430 exampleBtn.setEnabled(false);
431 textArea.setEnabled(false);
432 okBtn.setEnabled(false);
433 closeBtn.setEnabled(false);
434 backBtn.setEnabled(false);
438 public void fetch(String ids, boolean isAsync)
442 ids = textArea.getText();
446 textArea.setText(ids);
448 Component parent = null; // or this
449 String title = null; // or some title for the progress monitor
450 int min = AsyncFetchTask.STATE_INIT;
451 int max = AsyncFetchTask.STATE_DONE;
452 int msDelay = (isAsync ? 5 : 0);
453 new AsyncFetchTask(ids, parent, title, msDelay, min, max).execute();
457 protected void resetDialog()
459 exampleBtn.setEnabled(true);
460 textArea.setEnabled(true);
461 okBtn.setEnabled(true);
462 closeBtn.setEnabled(true);
463 backBtn.setEnabled(parentSearchPanel != null);
467 * This asynchronous class allows for a single-threaded state machine
468 * SwingWorker to process a list of requests from multiple sources.
470 * A standard ProcessMonitor could be attached to this task, but it is
476 private class AsyncFetchTask extends AsyncSwingWorker
479 protected boolean addToLast = false;
481 protected List<String> aresultq = new ArrayList<>();
483 protected List<String> presultTitle = new ArrayList<>();
485 protected List<AlignmentI> presult = new ArrayList<>();
487 protected List<AlignmentI> aresult = new ArrayList<>();
489 protected FeatureSettingsModelI preferredFeatureColours = null;
491 protected List<DbSourceProxy> sources;
493 protected Iterator<DbSourceProxy> sourceIterator;
495 protected String[] fetchArray;
497 protected List<String> fetchList;
499 protected Iterator<String> fetchIterator;
501 protected int fetchCount;
503 protected DbSourceProxy source;
505 protected String ids;
509 protected boolean isAsync;
511 protected Throwable taskError;
513 public AsyncFetchTask(String ids, Component owner, String title,
517 super(owner, title, delayMillis, min, max);
519 isAsync = (delayMillis != 0);
520 myID = Thread.currentThread().getId();
524 public void initAsync()
526 sources = jalview.ws.SequenceFetcher.getInstance()
527 .getSourceProxy((String) database.getSelectedItem());
528 sourceIterator = sources.iterator();
529 fetchArray = ids.trim().split(";");
530 fetchList = Arrays.asList(fetchArray);
533 private final static int STATE_INIT = 0;
535 private final static int STATE_NEXT_SOURCE = 10;
537 private final static int STATE_FETCH_SINGLE = 30;
539 private final static int STATE_FETCH_MULTIPLE = 40;
541 private final static int STATE_PROCESS = 50;
543 private final static int STATE_PARSE_RESULTS = 80;
545 private final static int STATE_DONE = 100;
547 protected static final int STATE_TASK_ERROR = 99;
550 public int doInBackgroundAsync(int progress)
552 System.out.println("SequenceFetcher.AsyncFetchTask " + isAsync + " "
558 case STATE_NEXT_SOURCE:
559 boolean doneFetching = (fetchIterator == null
560 || !fetchIterator.hasNext());
561 boolean havePending = (fetchList.size() > 0);
562 if (!sourceIterator.hasNext() || doneFetching && !havePending)
564 showProgress((presult.size() > 0)
565 ? MessageManager.getString("status.parsing_results")
566 : MessageManager.getString("status.processing"));
567 return STATE_PARSE_RESULTS;
569 source = sourceIterator.next();
572 // if we are here, we must have some pending still
573 fetchCount = fetchList.size();
574 fetchIterator = fetchList.iterator();
575 // save the remaining queries in the original array
576 fetchArray = fetchList.toArray(new String[fetchCount]);
578 fetchList = new ArrayList<>();
580 showProgress(MessageManager.formatMessage(
581 "status.fetching_sequence_queries_from", new String[]
582 { Integer.valueOf(fetchCount).toString(),
583 source.getDbName() }));
584 return (source.getMaximumQueryCount() == 1 ? STATE_FETCH_SINGLE
585 : STATE_FETCH_MULTIPLE);
586 case STATE_FETCH_SINGLE:
587 if (fetchIterator.hasNext())
589 return runSubtask(new Runnable()
595 // source only handles one accession id at a time
600 // for CrossRef2xmlTest only; to "allow the server to breathe"
601 // (but we are doing that already when isAsync is true)
604 String notFound = fetchSingleAccession(source,
605 fetchIterator.next(), aresultq, aresult);
606 if (notFound != null)
608 fetchList.add(notFound);
610 } catch (Throwable e)
617 return STATE_PROCESS;
618 case STATE_FETCH_MULTIPLE:
619 // proxy can fetch multiple accessions at one time
620 setProgressAsync(STATE_PROCESS);
621 return runSubtask(new Runnable()
629 fetchMultipleAccessions(source, fetchIterator, aresultq,
631 } catch (Throwable e)
639 // Stack results ready for opening in alignment windows
640 if (aresult != null && aresult.size() > 0)
642 FeatureSettingsModelI proxyColourScheme = source
643 .getFeatureColourScheme();
644 if (proxyColourScheme != null)
646 preferredFeatureColours = proxyColourScheme;
649 AlignmentI ar = null;
650 if (source.isAlignmentSource())
653 // new window for each result
654 while (aresult.size() > 0)
656 presult.add(aresult.remove(0));
657 presultTitle.add(aresultq.remove(0) + " "
658 + getDefaultRetrievalTitle());
664 if (addToLast && presult.size() > 0)
666 ar = presult.remove(presult.size() - 1);
667 titl = presultTitle.remove(presultTitle.size() - 1);
669 // concatenate all results in one window
670 while (aresult.size() > 0)
674 ar = aresult.remove(0);
678 ar.append(aresult.remove(0));
683 presultTitle.add(titl);
686 showProgress(MessageManager.getString("status.finshed_querying"));
687 return STATE_NEXT_SOURCE;
688 case STATE_PARSE_RESULTS:
689 while (presult.size() > 0)
691 parseResult(presult.remove(0), presultTitle.remove(0), null,
692 preferredFeatureColours);
695 case STATE_TASK_ERROR:
698 return 0; // arbitrary; ignored
703 protected void resume()
709 super.setPaused(false);
713 private int runSubtask(Runnable subtask)
716 Runnable r = new Runnable()
725 } catch (Throwable e)
729 if (checkError(taskError))
731 setProgressAsync(STATE_TASK_ERROR);
746 super.setPaused(true);
747 new Thread(r).start();
748 return 0; // arbitrary return -- ignored because we are paused.
751 return super.getProgressAsync();
755 public void doneAsync()
758 if (fetchList.size() > 0)
760 StringBuffer sb = new StringBuffer();
761 sb.append("Didn't retrieve the following "
762 + (fetchList.size() == 1 ? "query"
763 : fetchList.size() + " queries")
765 int l = sb.length(), lr = 0;
766 for (String s : fetchList)
768 if (l != sb.length())
772 if (lr - sb.length() > 40)
778 showErrorMessage(sb.toString());
786 * @return true if there was an error and we need to STOP
788 protected boolean checkError(Throwable e)
794 String problem = "retrieving " + ids + " from "
795 + database.getSelectedItem();
796 if (e instanceof Exception)
798 showErrorMessage("Error " + problem);
799 System.err.println("Retrieval failed for source ='"
800 + database.getSelectedItem() + "' and query\n'"
803 else if (e instanceof OutOfMemoryError)
805 showErrorMessage("Out of Memory when " + problem
806 + "\nPlease see the Jalview FAQ for instructions for increasing the memory available to Jalview.\n");
807 // option here to return false and quit this, but that is not how
808 // original code works - BH
813 showErrorMessage("Serious Error " + problem);
819 private void showProgress(String msg)
821 guiWindow.setProgressBar(msg, myID);
827 * Tries to fetch one or more accession ids from the database proxy
831 * the queries to fetch
833 * a successful queries list to add to
835 * a list of retrieved alignments to add to
837 * failed queries are added to this list
840 void fetchMultipleAccessions(DbSourceProxy proxy,
841 Iterator<String> accessions, List<String> aresultq,
842 List<AlignmentI> aresult, List<String> nextFetch) throws Exception
844 StringBuilder multiacc = new StringBuilder();
845 List<String> tosend = new ArrayList<>();
846 while (accessions.hasNext())
848 String nel = accessions.next();
850 multiacc.append(nel);
851 if (accessions.hasNext())
853 multiacc.append(proxy.getAccessionSeparator());
859 String query = multiacc.toString();
860 AlignmentI rslt = proxy.getSequenceRecords(query);
861 if (rslt == null || rslt.getHeight() == 0)
863 // no results - pass on all queries to next source
864 nextFetch.addAll(tosend);
870 if (tosend.size() > 1)
872 checkResultForQueries(rslt, tosend, nextFetch, proxy);
875 } catch (OutOfMemoryError oome)
877 new OOMWarning("fetching " + multiacc + " from "
878 + database.getSelectedItem(), oome, this);
883 * Query for a single accession id via the database proxy
888 * a list of successful queries to add to
890 * a list of retrieved alignments to add to
891 * @return null if the fetch was successful; the accession if not
893 String fetchSingleAccession(DbSourceProxy proxy, String accession,
894 List<String> aresultq, List<AlignmentI> aresult) throws Throwable
896 AlignmentI indres = proxy.getSequenceRecords(accession);
901 aresultq.add(accession);
907 * Checks which of the queries were successfully retrieved by searching the
908 * DBRefs of the retrieved sequences for a match. Any not found are added to
909 * the 'nextFetch' list.
916 void checkResultForQueries(AlignmentI rslt, List<String> queries,
917 List<String> nextFetch, DbSourceProxy proxy)
919 SequenceI[] rs = rslt.getSequencesArray();
921 for (String q : queries)
923 // BH 2019.01.25 dbr is never used.
924 // DBRefEntry dbr = new DBRefEntry();
925 // dbr.setSource(proxy.getDbSource());
926 // dbr.setVersion(null);
927 String accId = proxy.getAccessionIdFromQuery(q);
928 // dbr.setAccessionId(accId);
929 boolean rfound = false;
930 for (int r = 0, nr = rs.length; r < nr; r++)
934 List<DBRefEntry> found = DBRefUtils.searchRefs(rs[r].getDBRefs(),
936 if (!found.isEmpty())
952 * @return a standard title for any results retrieved using the currently
953 * selected source and settings
955 public String getDefaultRetrievalTitle()
957 return "Retrieved from " + database.getSelectedItem();
960 AlignmentI parseResult(AlignmentI al, String title,
961 FileFormatI currentFileFormat,
962 FeatureSettingsModelI preferredFeatureColours)
965 if (al != null && al.getHeight() > 0)
969 title = getDefaultRetrievalTitle();
971 if (alignFrame == null)
973 AlignFrame af = new AlignFrame(al, AlignFrame.DEFAULT_WIDTH,
974 AlignFrame.DEFAULT_HEIGHT);
975 if (currentFileFormat != null)
977 af.currentFileFormat = currentFileFormat;
980 List<SequenceI> alsqs = al.getSequences();
983 for (SequenceI sq : alsqs)
985 if (sq.getFeatures().hasFeatures())
987 af.setShowSeqFeatures(true);
993 if (preferredFeatureColours != null)
995 af.getViewport().applyFeaturesStyle(preferredFeatureColours);
997 if (Cache.getDefault(Preferences.HIDE_INTRONS, true))
999 af.hideFeatureColumns(SequenceOntologyI.EXON, false);
1001 Desktop.addInternalFrame(af, title, AlignFrame.DEFAULT_WIDTH,
1002 AlignFrame.DEFAULT_HEIGHT);
1004 af.setStatus(MessageManager
1005 .getString("label.successfully_pasted_alignment_file"));
1010 Cache.getDefault(Preferences.SHOW_FULLSCREEN, false));
1011 } catch (Exception ex)
1017 alignFrame.viewport.addAlignment(al, title);
1023 void showErrorMessage(final String error)
1026 javax.swing.SwingUtilities.invokeLater(new Runnable()
1031 JvOptionPane.showInternalMessageDialog(Desktop.getDesktopPane(),
1033 MessageManager.getString("label.error_retrieving_data"),
1034 JvOptionPane.WARNING_MESSAGE);
1039 public IProgressIndicator getProgressIndicator()
1041 return progressIndicator;
1044 public void setProgressIndicator(IProgressIndicator progressIndicator)
1046 this.progressIndicator = progressIndicator;
1050 * Hide this panel (on clicking the database button to open the database
1055 frame.setVisible(false);
1059 * Called to modify the search panel for embedding as an alternative tab of a
1060 * free text search panel. The database choice list is hidden (since the
1061 * choice has been made), and a Back button is made visible (which reopens the
1062 * Sequence Fetcher panel).
1064 * @param parentPanel
1066 public void embedIn(GFTSPanel parentPanel)
1068 database.setVisible(false);
1069 backBtn.setVisible(true);
1070 parentSearchPanel = parentPanel;