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)
440 isAsync &= Platform.isJS();
443 ids = textArea.getText();
447 textArea.setText(ids);
449 Component parent = null; // or this
450 String title = null; // or some title for the progress monitor
451 int min = AsyncFetchTask.STATE_INIT;
452 int max = AsyncFetchTask.STATE_DONE;
453 int msDelay = (isAsync ? 5 : 0);
454 new AsyncFetchTask(ids, parent, title, msDelay, min, max).execute();
458 protected void resetDialog()
460 exampleBtn.setEnabled(true);
461 textArea.setEnabled(true);
462 okBtn.setEnabled(true);
463 closeBtn.setEnabled(true);
464 backBtn.setEnabled(parentSearchPanel != null);
468 * This asynchronous class allows for a single-threaded state machine
469 * SwingWorker to process a list of requests from multiple sources.
471 * A standard ProcessMonitor could be attached to this task, but it is
477 private class AsyncFetchTask extends AsyncSwingWorker
480 private boolean addToLast = false;
482 private List<String> aresultq = new ArrayList<>();
484 private List<String> presultTitle = new ArrayList<>();
486 private List<AlignmentI> presult = new ArrayList<>();
488 private List<AlignmentI> aresult = new ArrayList<>();
490 private FeatureSettingsModelI preferredFeatureColours = null;
492 private List<DbSourceProxy> sources;
494 private Iterator<DbSourceProxy> sourceIterator;
496 private String[] fetchArray;
498 private List<String> fetchList;
500 private Iterator<String> fetchIterator;
502 private int fetchCount;
504 private DbSourceProxy source;
508 public AsyncFetchTask(String ids, Component owner, String title,
512 super(owner, title, delayMillis, min, max);
517 public void initAsync()
519 sources = jalview.ws.SequenceFetcher.getInstance()
520 .getSourceProxy((String) database.getSelectedItem());
521 sourceIterator = sources.iterator();
522 fetchArray = ids.trim().split(";");
523 fetchList = Arrays.asList(fetchArray);
526 private final static int STATE_INIT = 0;
528 private final static int STATE_NEXT_SOURCE = 10;
530 private final static int STATE_FETCH_SINGLE = 30;
532 private final static int STATE_FETCH_MULTIPLE = 40;
534 private final static int STATE_PROCESS = 50;
536 private final static int STATE_PARSE_RESULTS = 80;
538 private final static int STATE_DONE = 100;
541 public int doInBackgroundAsync(int progress)
546 case STATE_NEXT_SOURCE:
547 boolean doneFetching = (fetchIterator == null
548 || !fetchIterator.hasNext());
549 boolean havePending = (fetchList.size() > 0);
550 if (!sourceIterator.hasNext() || doneFetching && !havePending)
552 showProgress((presult.size() > 0)
553 ? MessageManager.getString("status.parsing_results")
554 : MessageManager.getString("status.processing"));
555 return STATE_PARSE_RESULTS;
557 source = sourceIterator.next();
560 // if we are here, we must have some pending still
561 fetchCount = fetchList.size();
562 fetchIterator = fetchList.iterator();
563 // save the remaining queries in the original array
564 fetchArray = fetchList.toArray(new String[fetchCount]);
566 fetchList = new ArrayList<>();
568 showProgress(MessageManager.formatMessage(
569 "status.fetching_sequence_queries_from", new String[]
570 { Integer.valueOf(fetchCount).toString(),
571 source.getDbName() }));
572 return (source.getMaximumQueryCount() == 1 ? STATE_FETCH_SINGLE
573 : STATE_FETCH_MULTIPLE);
574 case STATE_FETCH_SINGLE:
575 if (fetchIterator.hasNext())
577 // source only handles one accession id at a time
580 if (delayMillis == 0)
582 // for CrossRef2xmlTest only
585 String accession = fetchIterator.next();
586 if (!fetchSingleAccession(source, accession, aresultq, aresult))
588 fetchList.add(accession);
590 } catch (Throwable e)
597 return STATE_FETCH_SINGLE;
599 return STATE_PROCESS;
600 case STATE_FETCH_MULTIPLE:
601 // proxy can fetch multiple accessions at one time
604 fetchMultipleAccessions(source, fetchIterator, aresultq, aresult,
606 } catch (Throwable e)
613 return STATE_PROCESS;
615 // Stack results ready for opening in alignment windows
616 if (aresult != null && aresult.size() > 0)
618 FeatureSettingsModelI proxyColourScheme = source
619 .getFeatureColourScheme();
620 if (proxyColourScheme != null)
622 preferredFeatureColours = proxyColourScheme;
625 AlignmentI ar = null;
626 if (source.isAlignmentSource())
629 // new window for each result
630 while (aresult.size() > 0)
632 presult.add(aresult.remove(0));
633 presultTitle.add(aresultq.remove(0) + " "
634 + getDefaultRetrievalTitle());
640 if (addToLast && presult.size() > 0)
642 ar = presult.remove(presult.size() - 1);
643 titl = presultTitle.remove(presultTitle.size() - 1);
645 // concatenate all results in one window
646 while (aresult.size() > 0)
650 ar = aresult.remove(0);
654 ar.append(aresult.remove(0));
659 presultTitle.add(titl);
662 showProgress(MessageManager.getString("status.finshed_querying"));
663 return STATE_NEXT_SOURCE;
664 case STATE_PARSE_RESULTS:
665 while (presult.size() > 0)
667 parseResult(presult.remove(0), presultTitle.remove(0), null,
668 preferredFeatureColours);
675 private void showProgress(String msg)
677 guiWindow.setProgressBar(msg, Thread.currentThread().hashCode());
681 public void doneAsync()
684 if (fetchList.size() > 0)
686 StringBuffer sb = new StringBuffer();
687 sb.append("Didn't retrieve the following "
688 + (fetchList.size() == 1 ? "query"
689 : fetchList.size() + " queries")
691 int l = sb.length(), lr = 0;
692 for (String s : fetchList)
694 if (l != sb.length())
698 if (lr - sb.length() > 40)
704 showErrorMessage(sb.toString());
709 private boolean showError(Throwable e)
711 String problem = "retrieving " + ids + " from "
712 + database.getSelectedItem();
713 if (e instanceof Exception)
715 showErrorMessage("Error " + problem);
716 System.err.println("Retrieval failed for source ='"
717 + database.getSelectedItem() + "' and query\n'"
720 else if (e instanceof OutOfMemoryError)
722 showErrorMessage("Out of Memory when " + problem
723 + "\nPlease see the Jalview FAQ for instructions for increasing the memory available to Jalview.\n");
724 // option here to return false and quit this, but that is not how
725 // original code works - BH
730 showErrorMessage("Serious Error " + problem);
739 * Tries to fetch one or more accession ids from the database proxy
743 * the queries to fetch
745 * a successful queries list to add to
747 * a list of retrieved alignments to add to
749 * failed queries are added to this list
752 void fetchMultipleAccessions(DbSourceProxy proxy,
753 Iterator<String> accessions, List<String> aresultq,
754 List<AlignmentI> aresult, List<String> nextFetch) throws Exception
756 StringBuilder multiacc = new StringBuilder();
757 List<String> tosend = new ArrayList<>();
758 while (accessions.hasNext())
760 String nel = accessions.next();
762 multiacc.append(nel);
763 if (accessions.hasNext())
765 multiacc.append(proxy.getAccessionSeparator());
771 String query = multiacc.toString();
772 AlignmentI rslt = proxy.getSequenceRecords(query);
773 if (rslt == null || rslt.getHeight() == 0)
775 // no results - pass on all queries to next source
776 nextFetch.addAll(tosend);
782 if (tosend.size() > 1)
784 checkResultForQueries(rslt, tosend, nextFetch, proxy);
787 } catch (OutOfMemoryError oome)
789 new OOMWarning("fetching " + multiacc + " from "
790 + database.getSelectedItem(), oome, this);
795 * Query for a single accession id via the database proxy
800 * a list of successful queries to add to
802 * a list of retrieved alignments to add to
803 * @return true if the fetch was successful, else false
805 boolean fetchSingleAccession(DbSourceProxy proxy, String accession,
806 List<String> aresultq, List<AlignmentI> aresult)
808 boolean success = false;
811 // BH no longer necessary; we are doing 5-ms asynchronous delays all along
812 // if (aresult != null)
816 // // give the server a chance to breathe
818 // } catch (Exception e)
824 AlignmentI indres = null;
827 indres = proxy.getSequenceRecords(accession);
828 } catch (OutOfMemoryError oome)
831 "fetching " + accession + " from " + proxy.getDbName(),
836 aresultq.add(accession);
840 } catch (Exception e)
842 Cache.log.info("Error retrieving " + accession + " from "
843 + proxy.getDbName(), e);
849 * Checks which of the queries were successfully retrieved by searching the
850 * DBRefs of the retrieved sequences for a match. Any not found are added to
851 * the 'nextFetch' list.
858 void checkResultForQueries(AlignmentI rslt, List<String> queries,
859 List<String> nextFetch, DbSourceProxy proxy)
861 SequenceI[] rs = rslt.getSequencesArray();
863 for (String q : queries)
865 // BH 2019.01.25 dbr is never used.
866 // DBRefEntry dbr = new DBRefEntry();
867 // dbr.setSource(proxy.getDbSource());
868 // dbr.setVersion(null);
869 String accId = proxy.getAccessionIdFromQuery(q);
870 // dbr.setAccessionId(accId);
871 boolean rfound = false;
872 for (int r = 0, nr = rs.length; r < nr; r++)
876 List<DBRefEntry> found = DBRefUtils.searchRefs(rs[r].getDBRefs(),
878 if (!found.isEmpty())
894 * @return a standard title for any results retrieved using the currently
895 * selected source and settings
897 public String getDefaultRetrievalTitle()
899 return "Retrieved from " + database.getSelectedItem();
902 AlignmentI parseResult(AlignmentI al, String title,
903 FileFormatI currentFileFormat,
904 FeatureSettingsModelI preferredFeatureColours)
907 if (al != null && al.getHeight() > 0)
911 title = getDefaultRetrievalTitle();
913 if (alignFrame == null)
915 AlignFrame af = new AlignFrame(al, AlignFrame.DEFAULT_WIDTH,
916 AlignFrame.DEFAULT_HEIGHT);
917 if (currentFileFormat != null)
919 af.currentFileFormat = currentFileFormat;
922 List<SequenceI> alsqs = al.getSequences();
925 for (SequenceI sq : alsqs)
927 if (sq.getFeatures().hasFeatures())
929 af.setShowSeqFeatures(true);
935 if (preferredFeatureColours != null)
937 af.getViewport().applyFeaturesStyle(preferredFeatureColours);
939 if (Cache.getDefault(Preferences.HIDE_INTRONS, true))
941 af.hideFeatureColumns(SequenceOntologyI.EXON, false);
943 Desktop.addInternalFrame(af, title, AlignFrame.DEFAULT_WIDTH,
944 AlignFrame.DEFAULT_HEIGHT);
946 af.setStatus(MessageManager
947 .getString("label.successfully_pasted_alignment_file"));
952 Cache.getDefault(Preferences.SHOW_FULLSCREEN, false));
953 } catch (Exception ex)
959 alignFrame.viewport.addAlignment(al, title);
965 void showErrorMessage(final String error)
968 javax.swing.SwingUtilities.invokeLater(new Runnable()
973 JvOptionPane.showInternalMessageDialog(Desktop.getDesktopPane(),
975 MessageManager.getString("label.error_retrieving_data"),
976 JvOptionPane.WARNING_MESSAGE);
981 public IProgressIndicator getProgressIndicator()
983 return progressIndicator;
986 public void setProgressIndicator(IProgressIndicator progressIndicator)
988 this.progressIndicator = progressIndicator;
992 * Hide this panel (on clicking the database button to open the database
997 frame.setVisible(false);
1001 * Called to modify the search panel for embedding as an alternative tab of a
1002 * free text search panel. The database choice list is hidden (since the
1003 * choice has been made), and a Back button is made visible (which reopens the
1004 * Sequence Fetcher panel).
1006 * @param parentPanel
1008 public void embedIn(GFTSPanel parentPanel)
1010 database.setVisible(false);
1011 backBtn.setVisible(true);
1012 parentSearchPanel = parentPanel;