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 java.awt.BorderLayout;
25 import java.awt.event.ActionEvent;
26 import java.awt.event.ActionListener;
27 import java.awt.event.KeyAdapter;
28 import java.awt.event.KeyEvent;
29 import java.util.ArrayList;
30 import java.util.Arrays;
31 import java.util.HashSet;
32 import java.util.Iterator;
33 import java.util.List;
34 import java.util.concurrent.CompletableFuture;
36 import javax.swing.JButton;
37 import javax.swing.JCheckBox;
38 import javax.swing.JComboBox;
39 import javax.swing.JInternalFrame;
40 import javax.swing.JLabel;
41 import javax.swing.JPanel;
42 import javax.swing.JScrollPane;
43 import javax.swing.JTextArea;
44 import javax.swing.SwingConstants;
46 import jalview.api.FeatureSettingsModelI;
47 import jalview.bin.Cache;
48 import jalview.bin.Console;
49 import jalview.datamodel.AlignmentI;
50 import jalview.datamodel.DBRefEntry;
51 import jalview.datamodel.SequenceI;
52 import jalview.fts.core.GFTSPanel;
53 import jalview.fts.service.pdb.PDBFTSPanel;
54 import jalview.fts.service.threedbeacons.TDBeaconsFTSPanel;
55 import jalview.fts.service.uniprot.UniprotFTSPanel;
56 import jalview.io.FileFormatI;
57 import jalview.io.gff.SequenceOntologyI;
58 import jalview.util.DBRefUtils;
59 import jalview.util.MessageManager;
60 import jalview.util.Platform;
61 import jalview.ws.seqfetcher.DbSourceProxy;
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 public class SequenceFetcher extends JPanel implements Runnable
72 private class StringPair
76 private String display;
78 public StringPair(String s1, String s2)
84 public StringPair(String s)
89 public String getKey()
94 public String getDisplay()
100 public String toString()
105 public boolean equals(StringPair other)
107 return other.key == this.key;
111 private static jalview.ws.SequenceFetcher sfetch = null;
113 JLabel exampleAccession;
115 JComboBox<StringPair> database;
117 JCheckBox replacePunctuation;
129 JInternalFrame frame;
131 IProgressIndicator guiWindow;
133 AlignFrame alignFrame;
135 GFTSPanel parentSearchPanel;
137 IProgressIndicator progressIndicator;
139 volatile boolean _isConstructing = false;
142 * Returns the shared instance of the SequenceFetcher client
146 public static jalview.ws.SequenceFetcher getSequenceFetcherSingleton()
150 sfetch = new jalview.ws.SequenceFetcher();
156 * Constructor given a client to receive any status or progress messages
157 * (currently either the Desktop, or an AlignFrame panel)
161 public SequenceFetcher(IProgressIndicator guiIndic)
163 this(guiIndic, null, null);
167 * Constructor with specified database and accession(s) to retrieve
174 public SequenceFetcher(IProgressIndicator guiIndic,
175 final String selectedDb, final String queryString)
177 this(guiIndic, selectedDb, queryString, true);
180 public SequenceFetcher(IProgressIndicator guiIndic,
181 final String selectedDb, final String queryString,
184 this.progressIndicator = guiIndic;
185 getSequenceFetcherSingleton();
186 this.guiWindow = progressIndicator;
188 if (progressIndicator instanceof AlignFrame)
190 alignFrame = (AlignFrame) progressIndicator;
193 jbInit(selectedDb, interactive);
194 textArea.setText(queryString);
196 frame = new JInternalFrame();
197 frame.setContentPane(this);
198 Desktop.addInternalFrame(frame, getFrameTitle(), true, 400,
199 Platform.isAMacAndNotJS() ? 240 : 180);
202 private String getFrameTitle()
204 return ((alignFrame == null)
205 ? MessageManager.getString("label.new_sequence_fetcher")
207 .getString("label.additional_sequence_fetcher"));
210 private void jbInit(String selectedDb, boolean interactive)
212 this.setLayout(new BorderLayout());
214 database = new JComboBox<>();
215 database.setFont(JvSwingUtils.getLabelFont());
216 StringPair instructionItem = new StringPair(
217 MessageManager.getString("action.select_ddbb"));
218 database.setPrototypeDisplayValue(instructionItem);
219 String[] sources = new jalview.ws.SequenceFetcher().getSupportedDb();
220 Arrays.sort(sources, String.CASE_INSENSITIVE_ORDER);
221 database.addItem(instructionItem);
222 for (String source : sources)
224 List<DbSourceProxy> slist = sfetch.getSourceProxy(source);
225 if (slist.size() == 1 && slist.get(0) != null)
227 database.addItem(new StringPair(source, slist.get(0).getDbName()));
231 database.addItem(new StringPair(source));
234 setDatabaseSelectedItem(selectedDb);
235 if (database.getSelectedIndex() == -1)
237 database.setSelectedIndex(0);
239 database.setMaximumRowCount(database.getItemCount());
240 database.addActionListener(new ActionListener()
243 public void actionPerformed(ActionEvent e)
245 String currentSelection = ((StringPair) database.getSelectedItem())
247 updateExampleQuery(currentSelection);
249 if ("pdb".equalsIgnoreCase(currentSelection))
252 new PDBFTSPanel(SequenceFetcher.this);
254 else if ("uniprot".equalsIgnoreCase(currentSelection))
257 new UniprotFTSPanel(SequenceFetcher.this);
259 else if ("3d-beacons".equalsIgnoreCase(currentSelection))
262 new TDBeaconsFTSPanel(SequenceFetcher.this);
271 exampleAccession = new JLabel("");
272 exampleAccession.setFont(new Font("Verdana", Font.BOLD, 11));
273 JLabel jLabel1 = new JLabel(MessageManager
274 .getString("label.separate_multiple_accession_ids"));
275 jLabel1.setFont(new Font("Verdana", Font.ITALIC, 11));
276 jLabel1.setHorizontalAlignment(SwingConstants.LEFT);
278 replacePunctuation = new JCheckBox(
279 MessageManager.getString("label.replace_commas_semicolons"));
280 replacePunctuation.setHorizontalAlignment(SwingConstants.LEFT);
281 replacePunctuation.setFont(new Font("Verdana", Font.ITALIC, 11));
282 okBtn = new JButton(MessageManager.getString("action.ok"));
283 okBtn.addActionListener(new ActionListener()
286 public void actionPerformed(ActionEvent e)
288 ok_actionPerformed();
291 JButton clear = new JButton(MessageManager.getString("action.clear"));
292 clear.addActionListener(new ActionListener()
295 public void actionPerformed(ActionEvent e)
297 clear_actionPerformed();
301 exampleBtn = new JButton(MessageManager.getString("label.example"));
302 exampleBtn.addActionListener(new ActionListener()
305 public void actionPerformed(ActionEvent e)
307 example_actionPerformed();
310 closeBtn = new JButton(MessageManager.getString("action.cancel"));
311 closeBtn.addActionListener(new ActionListener()
314 public void actionPerformed(ActionEvent e)
316 close_actionPerformed(e);
319 backBtn = new JButton(MessageManager.getString("action.back"));
320 backBtn.addActionListener(new ActionListener()
323 public void actionPerformed(ActionEvent e)
325 parentSearchPanel.btn_back_ActionPerformed();
328 // back not visible unless embedded
329 backBtn.setVisible(false);
331 textArea = new JTextArea();
332 textArea.setFont(JvSwingUtils.getLabelFont());
333 textArea.setLineWrap(true);
334 textArea.addKeyListener(new KeyAdapter()
337 public void keyPressed(KeyEvent e)
339 if (e.getKeyCode() == KeyEvent.VK_ENTER)
341 ok_actionPerformed();
346 JPanel actionPanel = new JPanel();
347 actionPanel.add(backBtn);
348 actionPanel.add(exampleBtn);
349 actionPanel.add(clear);
350 actionPanel.add(okBtn);
351 actionPanel.add(closeBtn);
353 JPanel databasePanel = new JPanel();
354 databasePanel.setLayout(new BorderLayout());
355 databasePanel.add(database, BorderLayout.NORTH);
356 databasePanel.add(exampleAccession, BorderLayout.CENTER);
357 JPanel jPanel2a = new JPanel(new BorderLayout());
358 jPanel2a.add(jLabel1, BorderLayout.NORTH);
359 jPanel2a.add(replacePunctuation, BorderLayout.SOUTH);
360 databasePanel.add(jPanel2a, BorderLayout.SOUTH);
362 JPanel idsPanel = new JPanel();
363 idsPanel.setLayout(new BorderLayout(0, 5));
364 JScrollPane jScrollPane1 = new JScrollPane();
365 jScrollPane1.getViewport().add(textArea);
366 idsPanel.add(jScrollPane1, BorderLayout.CENTER);
368 // En/disable or show/hide interactive elements
369 database.setEnabled(interactive);
370 exampleAccession.setVisible(interactive);
371 replacePunctuation.setVisible(interactive);
372 okBtn.setVisible(interactive);
373 exampleBtn.setVisible(interactive);
374 closeBtn.setVisible(interactive);
375 backBtn.setVisible(interactive);
376 jLabel1.setVisible(interactive);
377 clear.setVisible(interactive);
378 textArea.setEnabled(interactive);
380 this.add(actionPanel, BorderLayout.SOUTH);
381 this.add(idsPanel, BorderLayout.CENTER);
382 this.add(databasePanel, BorderLayout.NORTH);
385 private void setDatabaseSelectedItem(String db)
387 for (int i = 0; i < database.getItemCount(); i++)
389 StringPair sp = database.getItemAt(i);
390 if (sp != null && db != null && db.equals(sp.getKey()))
392 database.setSelectedIndex(i);
399 * Answers a semi-colon-delimited string with the example query or queries for
400 * the selected database
405 protected String getExampleQueries(String db)
407 StringBuilder sb = new StringBuilder();
408 HashSet<String> hs = new HashSet<>();
409 for (DbSourceProxy dbs : sfetch.getSourceProxy(db))
411 String tq = dbs.getTestQuery();
412 if (hs.add(tq)) // not a duplicate source
421 return sb.toString();
425 * Action on selecting a database other than Uniprot or PDB is to enable or
426 * disable 'Replace commas', and await input in the query field
428 protected void otherSourceAction()
432 String eq = exampleAccession.getText();
433 // TODO this should be a property of the SequenceFetcher whether commas
434 // are allowed in the IDs...
436 boolean enablePunct = !(eq != null && eq.indexOf(",") > -1);
437 replacePunctuation.setEnabled(enablePunct);
439 } catch (Exception ex)
441 exampleAccession.setText("");
442 replacePunctuation.setEnabled(true);
448 * Sets the text of the example query to incorporate the example accession
449 * provided by the selected database source
451 * @param selectedDatabase
454 protected String updateExampleQuery(String selectedDatabase)
456 String eq = getExampleQueries(selectedDatabase);
457 exampleAccession.setText(MessageManager
458 .formatMessage("label.example_query_param", new String[]
464 * Action on clicking the 'Example' button is to write the example accession
465 * as the query text field value
467 protected void example_actionPerformed()
469 String eq = getExampleQueries(
470 ((StringPair) database.getSelectedItem()).getKey());
471 textArea.setText(eq);
476 * Clears the query input field
478 protected void clear_actionPerformed()
480 textArea.setText("");
485 * Action on Close button is to close this frame, and also (if it is embedded
486 * in a search panel) to close the search panel
490 public void close_actionPerformed(ActionEvent e)
494 frame.setClosed(true);
495 if (parentSearchPanel != null)
497 parentSearchPanel.btn_cancel_ActionPerformed();
499 } catch (Exception ex)
505 * Action on OK is to start the fetch for entered accession(s)
507 public void ok_actionPerformed()
509 ok_actionPerformed(false, null);
512 public CompletableFuture<Void> ok_actionPerformed(boolean returnFuture,
516 * tidy inputs and check there is something to search for
518 String t0 = textArea.getText();
519 String text = t0.trim();
520 if (replacePunctuation.isEnabled() && replacePunctuation.isSelected())
522 text = text.replace(",", ";");
524 text = text.replaceAll("(\\s|[; ])+", ";");
525 if (!t0.equals(text))
527 textArea.setText(text);
533 "Please enter a (semi-colon separated list of) database id(s)");
537 if (database.getSelectedIndex() == 0)
540 showErrorMessage("Please choose a database");
545 exampleBtn.setEnabled(false);
546 textArea.setEnabled(false);
547 okBtn.setEnabled(false);
548 closeBtn.setEnabled(false);
549 backBtn.setEnabled(false);
551 CompletableFuture<Void> worker = CompletableFuture
552 .runAsync(() -> runAndCacheAlignFrame(returnFuture, id));
554 return returnFuture ? worker : null;
557 private void runAndCacheAlignFrame(boolean cacheAlignFrame, String id)
559 AlignFrame af = this.run(cacheAlignFrame);
560 if (cacheAlignFrame && id != null && af != null)
561 af.cacheAlignFrameFromRestId(id);
564 private void resetDialog()
566 exampleBtn.setEnabled(true);
567 textArea.setEnabled(true);
568 okBtn.setEnabled(true);
569 closeBtn.setEnabled(true);
570 backBtn.setEnabled(parentSearchPanel != null);
579 public AlignFrame run(boolean returnAlignFrame)
581 boolean addToLast = false;
582 List<String> aresultq = new ArrayList<>();
583 List<String> presultTitle = new ArrayList<>();
584 List<AlignmentI> presult = new ArrayList<>();
585 List<AlignmentI> aresult = new ArrayList<>();
586 List<DbSourceProxy> sources = sfetch.getSourceProxy(
587 ((StringPair) database.getSelectedItem()).getKey());
588 Iterator<DbSourceProxy> proxies = sources.iterator();
589 String[] qries = textArea.getText().trim().split(";");
590 List<String> nextFetch = Arrays.asList(qries);
591 Iterator<String> en = Arrays.asList(new String[0]).iterator();
592 int nqueries = qries.length;
594 FeatureSettingsModelI preferredFeatureColours = null;
595 while (proxies.hasNext() && (en.hasNext() || nextFetch.size() > 0))
597 if (!en.hasNext() && nextFetch.size() > 0)
599 en = nextFetch.iterator();
600 nqueries = nextFetch.size();
601 // save the remaining queries in the original array
602 qries = nextFetch.toArray(new String[nqueries]);
603 nextFetch = new ArrayList<>();
606 DbSourceProxy proxy = proxies.next();
610 guiWindow.setProgressBar(MessageManager.formatMessage(
611 "status.fetching_sequence_queries_from", new String[]
612 { Integer.valueOf(nqueries).toString(),
613 proxy.getDbName() }),
614 Thread.currentThread().hashCode());
615 if (proxy.getMaximumQueryCount() == 1)
618 * proxy only handles one accession id at a time
622 String acc = en.next();
623 if (!fetchSingleAccession(proxy, acc, aresultq, aresult))
632 * proxy can fetch multiple accessions at one time
634 fetchMultipleAccessions(proxy, en, aresultq, aresult, nextFetch);
636 } catch (Exception e)
638 showErrorMessage("Error retrieving " + textArea.getText() + " from "
639 + ((StringPair) database.getSelectedItem()).getDisplay());
641 // +="Couldn't retrieve sequences from "+database.getSelectedItem();
642 System.err.println("Retrieval failed for source ='"
643 + ((StringPair) database.getSelectedItem()).getDisplay()
644 + "' and query\n'" + textArea.getText() + "'\n");
646 } catch (OutOfMemoryError e)
648 showErrorMessage("Out of Memory when retrieving "
649 + textArea.getText() + " from "
650 + ((StringPair) database.getSelectedItem()).getDisplay()
651 + "\nPlease see the Jalview FAQ for instructions for increasing the memory available to Jalview.\n");
655 showErrorMessage("Serious Error retrieving " + textArea.getText()
657 + ((StringPair) database.getSelectedItem()).getDisplay());
661 // Stack results ready for opening in alignment windows
662 if (aresult != null && aresult.size() > 0)
664 FeatureSettingsModelI proxyColourScheme = proxy
665 .getFeatureColourScheme();
666 if (proxyColourScheme != null)
668 preferredFeatureColours = proxyColourScheme;
671 AlignmentI ar = null;
672 if (proxy.isAlignmentSource())
675 // new window for each result
676 while (aresult.size() > 0)
678 presult.add(aresult.remove(0));
680 aresultq.remove(0) + " " + getDefaultRetrievalTitle());
686 if (addToLast && presult.size() > 0)
688 ar = presult.remove(presult.size() - 1);
689 titl = presultTitle.remove(presultTitle.size() - 1);
691 // concatenate all results in one window
692 while (aresult.size() > 0)
696 ar = aresult.remove(0);
700 ar.append(aresult.remove(0));
705 presultTitle.add(titl);
708 guiWindow.setProgressBar(
709 MessageManager.getString("status.finshed_querying"),
710 Thread.currentThread().hashCode());
716 .getString("status.parsing_results")
717 : MessageManager.getString("status.processing"),
718 Thread.currentThread().hashCode());
720 AlignFrame af = null;
721 while (presult.size() > 0)
723 af = parseResult(presult.remove(0), presultTitle.remove(0), null,
724 preferredFeatureColours);
726 // only remove visual delay after we finished parsing.
727 guiWindow.setProgressBar(null, Thread.currentThread().hashCode());
728 if (nextFetch.size() > 0)
730 StringBuffer sb = new StringBuffer();
731 sb.append("Didn't retrieve the following "
732 + (nextFetch.size() == 1 ? "query"
733 : nextFetch.size() + " queries")
735 int l = sb.length(), lr = 0;
736 for (String s : nextFetch)
738 if (l != sb.length())
742 if (lr - sb.length() > 40)
748 showErrorMessage(sb.toString());
751 return returnAlignFrame ? af : null;
755 * Tries to fetch one or more accession ids from the database proxy
759 * the queries to fetch
761 * a successful queries list to add to
763 * a list of retrieved alignments to add to
765 * failed queries are added to this list
768 void fetchMultipleAccessions(DbSourceProxy proxy,
769 Iterator<String> accessions, List<String> aresultq,
770 List<AlignmentI> aresult, List<String> nextFetch) throws Exception
772 StringBuilder multiacc = new StringBuilder();
773 List<String> tosend = new ArrayList<>();
774 while (accessions.hasNext())
776 String nel = accessions.next();
778 multiacc.append(nel);
779 if (accessions.hasNext())
781 multiacc.append(proxy.getAccessionSeparator());
787 String query = multiacc.toString();
788 AlignmentI rslt = proxy.getSequenceRecords(query);
789 if (rslt == null || rslt.getHeight() == 0)
791 // no results - pass on all queries to next source
792 nextFetch.addAll(tosend);
798 if (tosend.size() > 1)
800 checkResultForQueries(rslt, tosend, nextFetch, proxy);
803 } catch (OutOfMemoryError oome)
805 new OOMWarning("fetching " + multiacc + " from "
806 + ((StringPair) database.getSelectedItem()).getDisplay(),
812 * Query for a single accession id via the database proxy
817 * a list of successful queries to add to
819 * a list of retrieved alignments to add to
820 * @return true if the fetch was successful, else false
822 boolean fetchSingleAccession(DbSourceProxy proxy, String accession,
823 List<String> aresultq, List<AlignmentI> aresult)
825 boolean success = false;
832 // give the server a chance to breathe
834 } catch (Exception e)
840 AlignmentI indres = null;
843 indres = proxy.getSequenceRecords(accession);
844 } catch (OutOfMemoryError oome)
847 "fetching " + accession + " from " + proxy.getDbName(),
852 aresultq.add(accession);
856 } catch (Exception e)
858 Console.info("Error retrieving " + accession + " from "
859 + proxy.getDbName(), e);
865 * Checks which of the queries were successfully retrieved by searching the
866 * DBRefs of the retrieved sequences for a match. Any not found are added to
867 * the 'nextFetch' list.
874 void checkResultForQueries(AlignmentI rslt, List<String> queries,
875 List<String> nextFetch, DbSourceProxy proxy)
877 SequenceI[] rs = rslt.getSequencesArray();
879 for (String q : queries)
881 // BH 2019.01.25 dbr is never used.
882 // DBRefEntry dbr = new DBRefEntry();
883 // dbr.setSource(proxy.getDbSource());
884 // dbr.setVersion(null);
885 String accId = proxy.getAccessionIdFromQuery(q);
886 // dbr.setAccessionId(accId);
887 boolean rfound = false;
888 for (int r = 0, nr = rs.length; r < nr; r++)
892 List<DBRefEntry> found = DBRefUtils.searchRefs(rs[r].getDBRefs(),
894 if (!found.isEmpty())
910 * @return a standard title for any results retrieved using the currently
911 * selected source and settings
913 public String getDefaultRetrievalTitle()
915 return "Retrieved from "
916 + ((StringPair) database.getSelectedItem()).getDisplay();
920 * constructs an alignment frame given the data and metadata
924 * @param currentFileFormat
925 * @param preferredFeatureColours
926 * @return the alignment
928 public AlignFrame parseResult(AlignmentI al, String title,
929 FileFormatI currentFileFormat,
930 FeatureSettingsModelI preferredFeatureColours)
933 AlignFrame af = alignFrame;
934 if (al != null && al.getHeight() > 0)
938 title = getDefaultRetrievalTitle();
942 af = new AlignFrame(al, AlignFrame.DEFAULT_WIDTH,
943 AlignFrame.DEFAULT_HEIGHT);
944 if (currentFileFormat != null)
946 af.currentFileFormat = currentFileFormat;
949 List<SequenceI> alsqs = al.getSequences();
952 for (SequenceI sq : alsqs)
954 if (sq.getFeatures().hasFeatures())
956 af.setShowSeqFeatures(true);
962 af.getViewport().applyFeaturesStyle(preferredFeatureColours);
963 if (Cache.getDefault("HIDE_INTRONS", true))
965 af.hideFeatureColumns(SequenceOntologyI.EXON, false);
967 Desktop.addInternalFrame(af, title, AlignFrame.DEFAULT_WIDTH,
968 AlignFrame.DEFAULT_HEIGHT);
970 af.setStatus(MessageManager
971 .getString("label.successfully_pasted_alignment_file"));
975 af.setMaximum(Cache.getDefault("SHOW_FULLSCREEN", false));
976 } catch (Exception ex)
982 af.viewport.addAlignment(al, title);
988 void showErrorMessage(final String error)
991 javax.swing.SwingUtilities.invokeLater(new Runnable()
996 JvOptionPane.showInternalMessageDialog(Desktop.desktop, error,
997 MessageManager.getString("label.error_retrieving_data"),
998 JvOptionPane.WARNING_MESSAGE);
1003 public IProgressIndicator getProgressIndicator()
1005 return progressIndicator;
1008 public void setProgressIndicator(IProgressIndicator progressIndicator)
1010 this.progressIndicator = progressIndicator;
1014 * Hide this panel (on clicking the database button to open the database
1019 frame.setVisible(false);
1022 public void setQuery(String ids)
1024 textArea.setText(ids);
1028 * Called to modify the search panel for embedding as an alternative tab of a
1029 * free text search panel. The database choice list is hidden (since the
1030 * choice has been made), and a Back button is made visible (which reopens the
1031 * Sequence Fetcher panel).
1033 * @param parentPanel
1035 public void embedIn(GFTSPanel parentPanel)
1037 database.setVisible(false);
1038 backBtn.setVisible(true);
1039 parentSearchPanel = parentPanel;