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;
40 import java.awt.event.ActionEvent;
41 import java.awt.event.ActionListener;
42 import java.awt.event.KeyAdapter;
43 import java.awt.event.KeyEvent;
44 import java.util.ArrayList;
45 import java.util.Arrays;
46 import java.util.HashSet;
47 import java.util.Iterator;
48 import java.util.List;
50 import javax.swing.JButton;
51 import javax.swing.JCheckBox;
52 import javax.swing.JComboBox;
53 import javax.swing.JInternalFrame;
54 import javax.swing.JLabel;
55 import javax.swing.JPanel;
56 import javax.swing.JScrollPane;
57 import javax.swing.JTextArea;
58 import javax.swing.SwingConstants;
61 * A panel where the use may choose a database source, and enter one or more
62 * accessions, to retrieve entries from the database.
64 * If the selected source is Uniprot or PDB, a free text search panel is opened
65 * instead to perform the search and selection.
67 @SuppressWarnings("serial")
68 public class SequenceFetcher extends JPanel implements Runnable
71 JLabel exampleAccession;
73 JComboBox<String> database;
75 JCheckBox replacePunctuation;
89 IProgressIndicator guiWindow;
91 AlignFrame alignFrame;
93 GFTSPanel parentSearchPanel;
95 IProgressIndicator progressIndicator;
97 volatile boolean _isConstructing = false;
100 * Constructor given a client to receive any status or progress messages
101 * (currently either the Desktop, or an AlignFrame panel)
105 public SequenceFetcher(IProgressIndicator guiIndic)
107 this(guiIndic, null, null);
111 * Constructor with specified database and accession(s) to retrieve
117 public SequenceFetcher(IProgressIndicator guiIndic,
118 final String selectedDb, final String queryString)
120 this.progressIndicator = guiIndic;
122 this.guiWindow = progressIndicator;
124 if (progressIndicator instanceof AlignFrame)
126 alignFrame = (AlignFrame) progressIndicator;
130 textArea.setText(queryString);
132 frame = new JInternalFrame();
133 frame.setContentPane(this);
134 Desktop.addInternalFrame(frame, getFrameTitle(), true, 400,
135 Platform.isAMacAndNotJS() ? 240 : 180);
138 private String getFrameTitle()
140 return ((alignFrame == null)
141 ? MessageManager.getString("label.new_sequence_fetcher")
143 .getString("label.additional_sequence_fetcher"));
146 private void jbInit(String selectedDb)
148 this.setLayout(new BorderLayout());
150 database = new JComboBox<>();
151 database.setFont(JvSwingUtils.getLabelFont());
152 database.setPrototypeDisplayValue("ENSEMBLGENOMES ");
153 String[] sources = jalview.ws.SequenceFetcher.getInstance()
155 Arrays.sort(sources, String.CASE_INSENSITIVE_ORDER);
156 database.addItem(MessageManager.getString("action.select_ddbb"));
157 for (String source : sources)
159 database.addItem(source);
161 database.setSelectedItem(selectedDb);
162 if (database.getSelectedIndex() == -1)
164 database.setSelectedIndex(0);
166 database.setMaximumRowCount(database.getItemCount());
167 database.addActionListener(new ActionListener()
170 public void actionPerformed(ActionEvent e)
172 String currentSelection = (String) database.getSelectedItem();
173 updateExampleQuery(currentSelection);
175 if ("pdb".equalsIgnoreCase(currentSelection))
178 new PDBFTSPanel(SequenceFetcher.this);
180 else if ("uniprot".equalsIgnoreCase(currentSelection))
183 new UniprotFTSPanel(SequenceFetcher.this);
192 exampleAccession = new JLabel("");
193 exampleAccession.setFont(new Font("Verdana", Font.BOLD, 11));
194 JLabel jLabel1 = new JLabel(MessageManager
195 .getString("label.separate_multiple_accession_ids"));
196 jLabel1.setFont(new Font("Verdana", Font.ITALIC, 11));
197 jLabel1.setHorizontalAlignment(SwingConstants.LEFT);
199 replacePunctuation = new JCheckBox(
200 MessageManager.getString("label.replace_commas_semicolons"));
201 replacePunctuation.setHorizontalAlignment(SwingConstants.LEFT);
202 replacePunctuation.setFont(new Font("Verdana", Font.ITALIC, 11));
203 okBtn = new JButton(MessageManager.getString("action.ok"));
204 okBtn.addActionListener(new ActionListener()
207 public void actionPerformed(ActionEvent e)
209 ok_actionPerformed();
212 JButton clear = new JButton(MessageManager.getString("action.clear"));
213 clear.addActionListener(new ActionListener()
216 public void actionPerformed(ActionEvent e)
218 clear_actionPerformed();
222 exampleBtn = new JButton(MessageManager.getString("label.example"));
223 exampleBtn.addActionListener(new ActionListener()
226 public void actionPerformed(ActionEvent e)
228 example_actionPerformed();
231 closeBtn = new JButton(MessageManager.getString("action.cancel"));
232 closeBtn.addActionListener(new ActionListener()
235 public void actionPerformed(ActionEvent e)
237 close_actionPerformed(e);
240 backBtn = new JButton(MessageManager.getString("action.back"));
241 backBtn.addActionListener(new ActionListener()
244 public void actionPerformed(ActionEvent e)
246 parentSearchPanel.btn_back_ActionPerformed();
249 // back not visible unless embedded
250 backBtn.setVisible(false);
252 textArea = new JTextArea();
253 textArea.setFont(JvSwingUtils.getLabelFont());
254 textArea.setLineWrap(true);
255 textArea.addKeyListener(new KeyAdapter()
258 public void keyPressed(KeyEvent e)
260 if (e.getKeyCode() == KeyEvent.VK_ENTER)
262 ok_actionPerformed();
267 JPanel actionPanel = new JPanel();
268 actionPanel.add(backBtn);
269 actionPanel.add(exampleBtn);
270 actionPanel.add(clear);
271 actionPanel.add(okBtn);
272 actionPanel.add(closeBtn);
274 JPanel databasePanel = new JPanel();
275 databasePanel.setLayout(new BorderLayout());
276 databasePanel.add(database, BorderLayout.NORTH);
277 databasePanel.add(exampleAccession, BorderLayout.CENTER);
278 JPanel jPanel2a = new JPanel(new BorderLayout());
279 jPanel2a.add(jLabel1, BorderLayout.NORTH);
280 jPanel2a.add(replacePunctuation, BorderLayout.SOUTH);
281 databasePanel.add(jPanel2a, BorderLayout.SOUTH);
283 JPanel idsPanel = new JPanel();
284 idsPanel.setLayout(new BorderLayout(0, 5));
285 JScrollPane jScrollPane1 = new JScrollPane();
286 jScrollPane1.getViewport().add(textArea);
287 idsPanel.add(jScrollPane1, BorderLayout.CENTER);
289 this.add(actionPanel, BorderLayout.SOUTH);
290 this.add(idsPanel, BorderLayout.CENTER);
291 this.add(databasePanel, BorderLayout.NORTH);
295 * Answers a semi-colon-delimited string with the example query or queries for
296 * the selected database
301 protected String getExampleQueries(String db)
303 StringBuilder sb = new StringBuilder();
304 HashSet<String> hs = new HashSet<>();
305 for (DbSourceProxy dbs : jalview.ws.SequenceFetcher.getInstance()
308 String tq = dbs.getTestQuery();
309 if (hs.add(tq)) // not a duplicate source
318 return sb.toString();
322 * Action on selecting a database other than Uniprot or PDB is to enable or
323 * disable 'Replace commas', and await input in the query field
325 protected void otherSourceAction()
329 String eq = exampleAccession.getText();
330 // TODO this should be a property of the SequenceFetcher whether commas
331 // are allowed in the IDs...
333 boolean enablePunct = !(eq != null && eq.indexOf(",") > -1);
334 replacePunctuation.setEnabled(enablePunct);
336 } catch (Exception ex)
338 exampleAccession.setText("");
339 replacePunctuation.setEnabled(true);
345 * Sets the text of the example query to incorporate the example accession
346 * provided by the selected database source
348 * @param selectedDatabase
351 protected String updateExampleQuery(String selectedDatabase)
353 String eq = getExampleQueries(selectedDatabase);
354 exampleAccession.setText(MessageManager
355 .formatMessage("label.example_query_param", new String[]
361 * Action on clicking the 'Example' button is to write the example accession
362 * as the query text field value
364 protected void example_actionPerformed()
366 String eq = getExampleQueries((String) database.getSelectedItem());
367 textArea.setText(eq);
372 * Clears the query input field
374 protected void clear_actionPerformed()
376 textArea.setText("");
381 * Action on Close button is to close this frame, and also (if it is embedded
382 * in a search panel) to close the search panel
386 protected void close_actionPerformed(ActionEvent e)
390 frame.setClosed(true);
391 if (parentSearchPanel != null)
393 parentSearchPanel.btn_cancel_ActionPerformed();
395 } catch (Exception ex)
401 * Action on OK is to start the fetch for entered accession(s)
403 public void ok_actionPerformed()
406 * tidy inputs and check there is something to search for
408 String t0 = textArea.getText();
409 String text = t0.trim();
410 if (replacePunctuation.isEnabled() && replacePunctuation.isSelected())
412 text = text.replace(",", ";");
414 text = text.replaceAll("(\\s|[; ])+", ";");
415 if (!t0.equals(text))
417 textArea.setText(text);
423 "Please enter a (semi-colon separated list of) database id(s)");
427 exampleBtn.setEnabled(false);
428 textArea.setEnabled(false);
429 okBtn.setEnabled(false);
430 closeBtn.setEnabled(false);
431 backBtn.setEnabled(false);
433 Thread worker = new Thread(this);
437 private void resetDialog()
439 exampleBtn.setEnabled(true);
440 textArea.setEnabled(true);
441 okBtn.setEnabled(true);
442 closeBtn.setEnabled(true);
443 backBtn.setEnabled(parentSearchPanel != null);
449 boolean addToLast = false;
450 List<String> aresultq = new ArrayList<>();
451 List<String> presultTitle = new ArrayList<>();
452 List<AlignmentI> presult = new ArrayList<>();
453 List<AlignmentI> aresult = new ArrayList<>();
454 List<DbSourceProxy> sources = jalview.ws.SequenceFetcher.getInstance()
455 .getSourceProxy((String) database.getSelectedItem());
456 Iterator<DbSourceProxy> proxies = sources.iterator();
457 String[] qries = textArea.getText().trim().split(";");
458 List<String> nextFetch = Arrays.asList(qries);
459 Iterator<String> en = Arrays.asList(new String[0]).iterator();
460 int nqueries = qries.length;
462 FeatureSettingsModelI preferredFeatureColours = null;
463 while (proxies.hasNext() && (en.hasNext() || nextFetch.size() > 0))
465 if (!en.hasNext() && nextFetch.size() > 0)
467 en = nextFetch.iterator();
468 nqueries = nextFetch.size();
469 // save the remaining queries in the original array
470 qries = nextFetch.toArray(new String[nqueries]);
471 nextFetch = new ArrayList<>();
474 DbSourceProxy proxy = proxies.next();
478 guiWindow.setProgressBar(MessageManager.formatMessage(
479 "status.fetching_sequence_queries_from", new String[]
480 { Integer.valueOf(nqueries).toString(),
481 proxy.getDbName() }),
482 Thread.currentThread().hashCode());
483 if (proxy.getMaximumQueryCount() == 1)
486 * proxy only handles one accession id at a time
490 String acc = en.next();
491 if (!fetchSingleAccession(proxy, acc, aresultq, aresult))
500 * proxy can fetch multiple accessions at one time
502 fetchMultipleAccessions(proxy, en, aresultq, aresult, nextFetch);
504 } catch (Exception e)
506 showErrorMessage("Error retrieving " + textArea.getText() + " from "
507 + database.getSelectedItem());
509 // +="Couldn't retrieve sequences from "+database.getSelectedItem();
510 System.err.println("Retrieval failed for source ='"
511 + database.getSelectedItem() + "' and query\n'"
512 + textArea.getText() + "'\n");
514 } catch (OutOfMemoryError e)
516 showErrorMessage("Out of Memory when retrieving "
517 + textArea.getText() + " from " + database.getSelectedItem()
518 + "\nPlease see the Jalview FAQ for instructions for increasing the memory available to Jalview.\n");
522 showErrorMessage("Serious Error retrieving " + textArea.getText()
523 + " from " + database.getSelectedItem());
527 // Stack results ready for opening in alignment windows
528 if (aresult != null && aresult.size() > 0)
530 FeatureSettingsModelI proxyColourScheme = proxy
531 .getFeatureColourScheme();
532 if (proxyColourScheme != null)
534 preferredFeatureColours = proxyColourScheme;
537 AlignmentI ar = null;
538 if (proxy.isAlignmentSource())
541 // new window for each result
542 while (aresult.size() > 0)
544 presult.add(aresult.remove(0));
546 aresultq.remove(0) + " " + getDefaultRetrievalTitle());
552 if (addToLast && presult.size() > 0)
554 ar = presult.remove(presult.size() - 1);
555 titl = presultTitle.remove(presultTitle.size() - 1);
557 // concatenate all results in one window
558 while (aresult.size() > 0)
562 ar = aresult.remove(0);
566 ar.append(aresult.remove(0));
571 presultTitle.add(titl);
574 guiWindow.setProgressBar(
575 MessageManager.getString("status.finshed_querying"),
576 Thread.currentThread().hashCode());
582 .getString("status.parsing_results")
583 : MessageManager.getString("status.processing"),
584 Thread.currentThread().hashCode());
586 while (presult.size() > 0)
588 parseResult(presult.remove(0), presultTitle.remove(0), null,
589 preferredFeatureColours);
591 // only remove visual delay after we finished parsing.
592 guiWindow.setProgressBar(null, Thread.currentThread().hashCode());
593 if (nextFetch.size() > 0)
595 StringBuffer sb = new StringBuffer();
596 sb.append("Didn't retrieve the following "
597 + (nextFetch.size() == 1 ? "query"
598 : nextFetch.size() + " queries")
600 int l = sb.length(), lr = 0;
601 for (String s : nextFetch)
603 if (l != sb.length())
607 if (lr - sb.length() > 40)
613 showErrorMessage(sb.toString());
619 * Tries to fetch one or more accession ids from the database proxy
623 * the queries to fetch
625 * a successful queries list to add to
627 * a list of retrieved alignments to add to
629 * failed queries are added to this list
632 void fetchMultipleAccessions(DbSourceProxy proxy,
633 Iterator<String> accessions, List<String> aresultq,
634 List<AlignmentI> aresult, List<String> nextFetch) throws Exception
636 StringBuilder multiacc = new StringBuilder();
637 List<String> tosend = new ArrayList<>();
638 while (accessions.hasNext())
640 String nel = accessions.next();
642 multiacc.append(nel);
643 if (accessions.hasNext())
645 multiacc.append(proxy.getAccessionSeparator());
651 String query = multiacc.toString();
652 AlignmentI rslt = proxy.getSequenceRecords(query);
653 if (rslt == null || rslt.getHeight() == 0)
655 // no results - pass on all queries to next source
656 nextFetch.addAll(tosend);
662 if (tosend.size() > 1)
664 checkResultForQueries(rslt, tosend, nextFetch, proxy);
667 } catch (OutOfMemoryError oome)
669 new OOMWarning("fetching " + multiacc + " from "
670 + database.getSelectedItem(), oome, this);
675 * Query for a single accession id via the database proxy
680 * a list of successful queries to add to
682 * a list of retrieved alignments to add to
683 * @return true if the fetch was successful, else false
685 boolean fetchSingleAccession(DbSourceProxy proxy, String accession,
686 List<String> aresultq, List<AlignmentI> aresult)
688 boolean success = false;
695 // give the server a chance to breathe
697 } catch (Exception e)
703 AlignmentI indres = null;
706 indres = proxy.getSequenceRecords(accession);
707 } catch (OutOfMemoryError oome)
710 "fetching " + accession + " from " + proxy.getDbName(),
715 aresultq.add(accession);
719 } catch (Exception e)
721 Cache.log.info("Error retrieving " + accession + " from "
722 + proxy.getDbName(), e);
728 * Checks which of the queries were successfully retrieved by searching the
729 * DBRefs of the retrieved sequences for a match. Any not found are added to
730 * the 'nextFetch' list.
737 void checkResultForQueries(AlignmentI rslt, List<String> queries,
738 List<String> nextFetch, DbSourceProxy proxy)
740 SequenceI[] rs = rslt.getSequencesArray();
742 for (String q : queries)
744 // BH 2019.01.25 dbr is never used.
745 // DBRefEntry dbr = new DBRefEntry();
746 // dbr.setSource(proxy.getDbSource());
747 // dbr.setVersion(null);
748 String accId = proxy.getAccessionIdFromQuery(q);
749 // dbr.setAccessionId(accId);
750 boolean rfound = false;
751 for (int r = 0, nr = rs.length; r < nr; r++)
755 List<DBRefEntry> found = DBRefUtils.searchRefs(rs[r].getDBRefs(),
757 if (!found.isEmpty())
773 * @return a standard title for any results retrieved using the currently
774 * selected source and settings
776 public String getDefaultRetrievalTitle()
778 return "Retrieved from " + database.getSelectedItem();
781 AlignmentI parseResult(AlignmentI al, String title,
782 FileFormatI currentFileFormat,
783 FeatureSettingsModelI preferredFeatureColours)
786 if (al != null && al.getHeight() > 0)
790 title = getDefaultRetrievalTitle();
792 if (alignFrame == null)
794 AlignFrame af = new AlignFrame(al, AlignFrame.DEFAULT_WIDTH,
795 AlignFrame.DEFAULT_HEIGHT);
796 if (currentFileFormat != null)
798 af.currentFileFormat = currentFileFormat;
801 List<SequenceI> alsqs = al.getSequences();
804 for (SequenceI sq : alsqs)
806 if (sq.getFeatures().hasFeatures())
808 af.setShowSeqFeatures(true);
814 if (preferredFeatureColours != null)
816 af.getViewport().applyFeaturesStyle(preferredFeatureColours);
818 if (Cache.getDefault(Preferences.HIDE_INTRONS, true))
820 af.hideFeatureColumns(SequenceOntologyI.EXON, false);
822 Desktop.addInternalFrame(af, title, AlignFrame.DEFAULT_WIDTH,
823 AlignFrame.DEFAULT_HEIGHT);
825 af.setStatus(MessageManager
826 .getString("label.successfully_pasted_alignment_file"));
831 Cache.getDefault(Preferences.SHOW_FULLSCREEN, false));
832 } catch (Exception ex)
838 alignFrame.viewport.addAlignment(al, title);
844 void showErrorMessage(final String error)
847 javax.swing.SwingUtilities.invokeLater(new Runnable()
852 JvOptionPane.showInternalMessageDialog(Desktop.getDesktopPane(), error,
853 MessageManager.getString("label.error_retrieving_data"),
854 JvOptionPane.WARNING_MESSAGE);
859 public IProgressIndicator getProgressIndicator()
861 return progressIndicator;
864 public void setProgressIndicator(IProgressIndicator progressIndicator)
866 this.progressIndicator = progressIndicator;
870 * Hide this panel (on clicking the database button to open the database
875 frame.setVisible(false);
878 public void setQuery(String ids)
880 textArea.setText(ids);
884 * Called to modify the search panel for embedding as an alternative tab of a
885 * free text search panel. The database choice list is hidden (since the
886 * choice has been made), and a Back button is made visible (which reopens the
887 * Sequence Fetcher panel).
891 public void embedIn(GFTSPanel parentPanel)
893 database.setVisible(false);
894 backBtn.setVisible(true);
895 parentSearchPanel = parentPanel;