/* * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$) * Copyright (C) $$Year-Rel$$ The Jalview Authors * * This file is part of Jalview. * * Jalview is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation, either version 3 * of the License, or (at your option) any later version. * * Jalview is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty * of MERCHANTABILITY or FITNESS FOR A PARTICULAR * PURPOSE. See the GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with Jalview. If not, see . * The Jalview Authors are detailed in the 'AUTHORS' file. */ package jalview.gui; import java.awt.BorderLayout; import java.awt.Font; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.KeyAdapter; import java.awt.event.KeyEvent; import java.util.ArrayList; import java.util.Arrays; import java.util.HashSet; import java.util.Iterator; import java.util.List; import javax.swing.JButton; import javax.swing.JCheckBox; import javax.swing.JComboBox; import javax.swing.JInternalFrame; import javax.swing.JLabel; import javax.swing.JPanel; import javax.swing.JScrollPane; import javax.swing.JTextArea; import javax.swing.SwingConstants; import jalview.api.FeatureSettingsModelI; import jalview.bin.Cache; import jalview.bin.Console; import jalview.datamodel.AlignmentI; import jalview.datamodel.DBRefEntry; import jalview.datamodel.SequenceI; import jalview.fts.core.GFTSPanel; import jalview.fts.service.pdb.PDBFTSPanel; import jalview.fts.service.threedbeacons.TDBeaconsFTSPanel; import jalview.fts.service.uniprot.UniprotFTSPanel; import jalview.io.FileFormatI; import jalview.io.gff.SequenceOntologyI; import jalview.util.DBRefUtils; import jalview.util.MessageManager; import jalview.util.Platform; import jalview.ws.seqfetcher.DbSourceProxy; /** * A panel where the use may choose a database source, and enter one or more * accessions, to retrieve entries from the database. *

* If the selected source is Uniprot or PDB, a free text search panel is opened * instead to perform the search and selection. */ public class SequenceFetcher extends JPanel implements Runnable { private class StringPair { private String key; private String display; public StringPair(String s1, String s2) { key = s1; display = s2; } public StringPair(String s) { this(s, s); } public String getKey() { return key; } public String getDisplay() { return display; } @Override public String toString() { return display; } public boolean equals(StringPair other) { return other.key == this.key; } } private static jalview.ws.SequenceFetcher sfetch = null; JLabel exampleAccession; JComboBox database; JCheckBox replacePunctuation; JButton okBtn; JButton exampleBtn; JButton closeBtn; JButton backBtn; JTextArea textArea; JInternalFrame frame; IProgressIndicator guiWindow; AlignFrame alignFrame; GFTSPanel parentSearchPanel; IProgressIndicator progressIndicator; volatile boolean _isConstructing = false; /** * Returns the shared instance of the SequenceFetcher client * * @return */ public static jalview.ws.SequenceFetcher getSequenceFetcherSingleton() { if (sfetch == null) { sfetch = new jalview.ws.SequenceFetcher(); } return sfetch; } /** * Constructor given a client to receive any status or progress messages * (currently either the Desktop, or an AlignFrame panel) * * @param guiIndic */ public SequenceFetcher(IProgressIndicator guiIndic) { this(guiIndic, null, null); } /** * Constructor with specified database and accession(s) to retrieve * * @param guiIndic * @param selectedDb * @param queryString */ public SequenceFetcher(IProgressIndicator guiIndic, final String selectedDb, final String queryString) { this.progressIndicator = guiIndic; getSequenceFetcherSingleton(); this.guiWindow = progressIndicator; if (progressIndicator instanceof AlignFrame) { alignFrame = (AlignFrame) progressIndicator; } jbInit(selectedDb); textArea.setText(queryString); frame = new JInternalFrame(); frame.setContentPane(this); Desktop.addInternalFrame(frame, getFrameTitle(), true, 400, Platform.isAMacAndNotJS() ? 240 : 180); } private String getFrameTitle() { return ((alignFrame == null) ? MessageManager.getString("label.new_sequence_fetcher") : MessageManager .getString("label.additional_sequence_fetcher")); } private void jbInit(String selectedDb) { this.setLayout(new BorderLayout()); database = new JComboBox<>(); database.setFont(JvSwingUtils.getLabelFont()); StringPair instructionItem = new StringPair( MessageManager.getString("action.select_ddbb")); database.setPrototypeDisplayValue(instructionItem); String[] sources = new jalview.ws.SequenceFetcher().getSupportedDb(); Arrays.sort(sources, String.CASE_INSENSITIVE_ORDER); database.addItem(instructionItem); for (String source : sources) { List slist = sfetch.getSourceProxy(source); if (slist.size() == 1 && slist.get(0) != null) { database.addItem(new StringPair(source, slist.get(0).getDbName())); } else { database.addItem(new StringPair(source)); } } setDatabaseSelectedItem(selectedDb); if (database.getSelectedIndex() == -1) { database.setSelectedIndex(0); } database.setMaximumRowCount(database.getItemCount()); database.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { String currentSelection = ((StringPair) database.getSelectedItem()) .getKey(); updateExampleQuery(currentSelection); if ("pdb".equalsIgnoreCase(currentSelection)) { frame.dispose(); new PDBFTSPanel(SequenceFetcher.this); } else if ("uniprot".equalsIgnoreCase(currentSelection)) { frame.dispose(); new UniprotFTSPanel(SequenceFetcher.this); } else if ("3d-beacons".equalsIgnoreCase(currentSelection)) { frame.dispose(); new TDBeaconsFTSPanel(SequenceFetcher.this); } else { otherSourceAction(); } } }); exampleAccession = new JLabel(""); exampleAccession.setFont(new Font("Verdana", Font.BOLD, 11)); JLabel jLabel1 = new JLabel(MessageManager .getString("label.separate_multiple_accession_ids")); jLabel1.setFont(new Font("Verdana", Font.ITALIC, 11)); jLabel1.setHorizontalAlignment(SwingConstants.LEFT); replacePunctuation = new JCheckBox( MessageManager.getString("label.replace_commas_semicolons")); replacePunctuation.setHorizontalAlignment(SwingConstants.LEFT); replacePunctuation.setFont(new Font("Verdana", Font.ITALIC, 11)); okBtn = new JButton(MessageManager.getString("action.ok")); okBtn.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { ok_actionPerformed(); } }); JButton clear = new JButton(MessageManager.getString("action.clear")); clear.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { clear_actionPerformed(); } }); exampleBtn = new JButton(MessageManager.getString("label.example")); exampleBtn.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { example_actionPerformed(); } }); closeBtn = new JButton(MessageManager.getString("action.cancel")); closeBtn.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { close_actionPerformed(e); } }); backBtn = new JButton(MessageManager.getString("action.back")); backBtn.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { parentSearchPanel.btn_back_ActionPerformed(); } }); // back not visible unless embedded backBtn.setVisible(false); textArea = new JTextArea(); textArea.setFont(JvSwingUtils.getLabelFont()); textArea.setLineWrap(true); textArea.addKeyListener(new KeyAdapter() { @Override public void keyPressed(KeyEvent e) { if (e.getKeyCode() == KeyEvent.VK_ENTER) { ok_actionPerformed(); } } }); JPanel actionPanel = new JPanel(); actionPanel.add(backBtn); actionPanel.add(exampleBtn); actionPanel.add(clear); actionPanel.add(okBtn); actionPanel.add(closeBtn); JPanel databasePanel = new JPanel(); databasePanel.setLayout(new BorderLayout()); databasePanel.add(database, BorderLayout.NORTH); databasePanel.add(exampleAccession, BorderLayout.CENTER); JPanel jPanel2a = new JPanel(new BorderLayout()); jPanel2a.add(jLabel1, BorderLayout.NORTH); jPanel2a.add(replacePunctuation, BorderLayout.SOUTH); databasePanel.add(jPanel2a, BorderLayout.SOUTH); JPanel idsPanel = new JPanel(); idsPanel.setLayout(new BorderLayout(0, 5)); JScrollPane jScrollPane1 = new JScrollPane(); jScrollPane1.getViewport().add(textArea); idsPanel.add(jScrollPane1, BorderLayout.CENTER); this.add(actionPanel, BorderLayout.SOUTH); this.add(idsPanel, BorderLayout.CENTER); this.add(databasePanel, BorderLayout.NORTH); } private void setDatabaseSelectedItem(String db) { for (int i = 0; i < database.getItemCount(); i++) { StringPair sp = database.getItemAt(i); if (sp != null && db != null && db.equals(sp.getKey())) { database.setSelectedIndex(i); return; } } } /** * Answers a semi-colon-delimited string with the example query or queries for * the selected database * * @param db * @return */ protected String getExampleQueries(String db) { StringBuilder sb = new StringBuilder(); HashSet hs = new HashSet<>(); for (DbSourceProxy dbs : sfetch.getSourceProxy(db)) { String tq = dbs.getTestQuery(); if (hs.add(tq)) // not a duplicate source { if (sb.length() > 0) { sb.append(";"); } sb.append(tq); } } return sb.toString(); } /** * Action on selecting a database other than Uniprot or PDB is to enable or * disable 'Replace commas', and await input in the query field */ protected void otherSourceAction() { try { String eq = exampleAccession.getText(); // TODO this should be a property of the SequenceFetcher whether commas // are allowed in the IDs... boolean enablePunct = !(eq != null && eq.indexOf(",") > -1); replacePunctuation.setEnabled(enablePunct); } catch (Exception ex) { exampleAccession.setText(""); replacePunctuation.setEnabled(true); } repaint(); } /** * Sets the text of the example query to incorporate the example accession * provided by the selected database source * * @param selectedDatabase * @return */ protected String updateExampleQuery(String selectedDatabase) { String eq = getExampleQueries(selectedDatabase); exampleAccession.setText(MessageManager .formatMessage("label.example_query_param", new String[] { eq })); return eq; } /** * Action on clicking the 'Example' button is to write the example accession * as the query text field value */ protected void example_actionPerformed() { String eq = getExampleQueries( ((StringPair) database.getSelectedItem()).getKey()); textArea.setText(eq); repaint(); } /** * Clears the query input field */ protected void clear_actionPerformed() { textArea.setText(""); repaint(); } /** * Action on Close button is to close this frame, and also (if it is embedded * in a search panel) to close the search panel * * @param e */ protected void close_actionPerformed(ActionEvent e) { try { frame.setClosed(true); if (parentSearchPanel != null) { parentSearchPanel.btn_cancel_ActionPerformed(); } } catch (Exception ex) { } } /** * Action on OK is to start the fetch for entered accession(s) */ public void ok_actionPerformed() { /* * tidy inputs and check there is something to search for */ String t0 = textArea.getText(); String text = t0.trim(); if (replacePunctuation.isEnabled() && replacePunctuation.isSelected()) { text = text.replace(",", ";"); } text = text.replaceAll("(\\s|[; ])+", ";"); if (!t0.equals(text)) { textArea.setText(text); } if (text.isEmpty()) { // todo i18n showErrorMessage( "Please enter a (semi-colon separated list of) database id(s)"); resetDialog(); return; } if (database.getSelectedIndex() == 0) { // todo i18n showErrorMessage("Please choose a database"); resetDialog(); return; } exampleBtn.setEnabled(false); textArea.setEnabled(false); okBtn.setEnabled(false); closeBtn.setEnabled(false); backBtn.setEnabled(false); Thread worker = new Thread(this); worker.start(); } private void resetDialog() { exampleBtn.setEnabled(true); textArea.setEnabled(true); okBtn.setEnabled(true); closeBtn.setEnabled(true); backBtn.setEnabled(parentSearchPanel != null); } @Override public void run() { boolean addToLast = false; List aresultq = new ArrayList<>(); List presultTitle = new ArrayList<>(); List presult = new ArrayList<>(); List aresult = new ArrayList<>(); List sources = sfetch.getSourceProxy( ((StringPair) database.getSelectedItem()).getKey()); 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 { // update status guiWindow.setProgressBar(MessageManager.formatMessage( "status.fetching_sequence_queries_from", new String[] { Integer.valueOf(nqueries).toString(), proxy.getDbName() }), Thread.currentThread().hashCode()); if (proxy.getMaximumQueryCount() == 1) { /* * proxy only handles one accession id at a time */ while (en.hasNext()) { String acc = en.next(); if (!fetchSingleAccession(proxy, acc, aresultq, aresult)) { nextFetch.add(acc); } } } else { /* * proxy can fetch multiple accessions at one time */ fetchMultipleAccessions(proxy, en, aresultq, aresult, nextFetch); } } catch (Exception e) { showErrorMessage("Error retrieving " + textArea.getText() + " from " + ((StringPair) database.getSelectedItem()).getDisplay()); // error // +="Couldn't retrieve sequences from "+database.getSelectedItem(); System.err.println("Retrieval failed for source ='" + ((StringPair) database.getSelectedItem()).getDisplay() + "' and query\n'" + textArea.getText() + "'\n"); e.printStackTrace(); } catch (OutOfMemoryError e) { showErrorMessage("Out of Memory when retrieving " + textArea.getText() + " from " + ((StringPair) database.getSelectedItem()).getDisplay() + "\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 " + ((StringPair) database.getSelectedItem()).getDisplay()); e.printStackTrace(); } // Stack results ready for opening in alignment windows if (aresult != null && aresult.size() > 0) { FeatureSettingsModelI proxyColourScheme = proxy .getFeatureColourScheme(); if (proxyColourScheme != null) { preferredFeatureColours = proxyColourScheme; } AlignmentI ar = null; if (proxy.isAlignmentSource()) { addToLast = false; // new window for each result while (aresult.size() > 0) { presult.add(aresult.remove(0)); presultTitle.add( aresultq.remove(0) + " " + getDefaultRetrievalTitle()); } } else { String titl = null; if (addToLast && presult.size() > 0) { ar = presult.remove(presult.size() - 1); titl = presultTitle.remove(presultTitle.size() - 1); } // concatenate all results in one window while (aresult.size() > 0) { if (ar == null) { ar = aresult.remove(0); } else { ar.append(aresult.remove(0)); } } addToLast = true; presult.add(ar); presultTitle.add(titl); } } guiWindow.setProgressBar( MessageManager.getString("status.finshed_querying"), Thread.currentThread().hashCode()); } 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); } // 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) { if (l != sb.length()) { sb.append("; "); } if (lr - sb.length() > 40) { sb.append("\n"); } sb.append(s); } showErrorMessage(sb.toString()); } resetDialog(); } /** * Tries to fetch one or more accession ids from the database proxy * * @param proxy * @param accessions * the queries to fetch * @param aresultq * a successful queries list to add to * @param aresult * a list of retrieved alignments to add to * @param nextFetch * failed queries are added to this list * @throws Exception */ void fetchMultipleAccessions(DbSourceProxy proxy, Iterator accessions, List aresultq, List aresult, List nextFetch) throws Exception { StringBuilder multiacc = new StringBuilder(); List tosend = new ArrayList<>(); while (accessions.hasNext()) { String nel = accessions.next(); tosend.add(nel); multiacc.append(nel); if (accessions.hasNext()) { multiacc.append(proxy.getAccessionSeparator()); } } try { String query = multiacc.toString(); AlignmentI rslt = proxy.getSequenceRecords(query); if (rslt == null || rslt.getHeight() == 0) { // no results - pass on all queries to next source nextFetch.addAll(tosend); } else { aresultq.add(query); aresult.add(rslt); if (tosend.size() > 1) { checkResultForQueries(rslt, tosend, nextFetch, proxy); } } } catch (OutOfMemoryError oome) { new OOMWarning("fetching " + multiacc + " from " + ((StringPair) database.getSelectedItem()).getDisplay(), oome, this); } } /** * Query for a single accession id via the database proxy * * @param proxy * @param accession * @param aresultq * a list of successful queries to add to * @param aresult * a list of retrieved alignments to add to * @return true if the fetch was successful, else false */ boolean fetchSingleAccession(DbSourceProxy proxy, String accession, List aresultq, List aresult) { boolean success = false; try { if (aresult != null) { try { // give the server a chance to breathe Thread.sleep(5); } catch (Exception e) { // } } AlignmentI indres = null; try { indres = proxy.getSequenceRecords(accession); } catch (OutOfMemoryError oome) { new OOMWarning( "fetching " + accession + " from " + proxy.getDbName(), oome, this); } if (indres != null) { aresultq.add(accession); aresult.add(indres); success = true; } } catch (Exception e) { Console.info("Error retrieving " + accession + " from " + proxy.getDbName(), e); } return success; } /** * Checks which of the queries were successfully retrieved by searching the * DBRefs of the retrieved sequences for a match. Any not found are added to * the 'nextFetch' list. * * @param rslt * @param queries * @param nextFetch * @param proxy */ void checkResultForQueries(AlignmentI rslt, List queries, List nextFetch, DbSourceProxy proxy) { SequenceI[] rs = rslt.getSequencesArray(); for (String q : queries) { // 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); boolean rfound = false; for (int r = 0, nr = rs.length; r < nr; r++) { if (rs[r] != null) { List found = DBRefUtils.searchRefs(rs[r].getDBRefs(), accId); if (!found.isEmpty()) { rfound = true; break; } } } if (!rfound) { nextFetch.add(q); } } } /** * * @return a standard title for any results retrieved using the currently * selected source and settings */ public String getDefaultRetrievalTitle() { return "Retrieved from " + ((StringPair) database.getSelectedItem()).getDisplay(); } /** * constructs an alignment frame given the data and metadata * * @param al * @param title * @param currentFileFormat * @param preferredFeatureColours * @return the alignment */ public AlignmentI parseResult(AlignmentI al, String title, FileFormatI currentFileFormat, FeatureSettingsModelI preferredFeatureColours) { if (al != null && al.getHeight() > 0) { if (title == null) { title = getDefaultRetrievalTitle(); } if (alignFrame == null) { AlignFrame af = new AlignFrame(al, AlignFrame.DEFAULT_WIDTH, AlignFrame.DEFAULT_HEIGHT); if (currentFileFormat != null) { af.currentFileFormat = currentFileFormat; } List alsqs = al.getSequences(); synchronized (alsqs) { for (SequenceI sq : alsqs) { if (sq.getFeatures().hasFeatures()) { af.setShowSeqFeatures(true); break; } } } af.getViewport().applyFeaturesStyle(preferredFeatureColours); if (Cache.getDefault("HIDE_INTRONS", true)) { af.hideFeatureColumns(SequenceOntologyI.EXON, false); } Desktop.addInternalFrame(af, title, AlignFrame.DEFAULT_WIDTH, AlignFrame.DEFAULT_HEIGHT); af.setStatus(MessageManager .getString("label.successfully_pasted_alignment_file")); try { af.setMaximum(Cache.getDefault("SHOW_FULLSCREEN", false)); } catch (Exception ex) { } } else { alignFrame.viewport.addAlignment(al, title); } } return al; } void showErrorMessage(final String error) { resetDialog(); javax.swing.SwingUtilities.invokeLater(new Runnable() { @Override public void run() { JvOptionPane.showInternalMessageDialog(Desktop.desktop, error, MessageManager.getString("label.error_retrieving_data"), JvOptionPane.WARNING_MESSAGE); } }); } public IProgressIndicator getProgressIndicator() { return progressIndicator; } public void setProgressIndicator(IProgressIndicator progressIndicator) { this.progressIndicator = progressIndicator; } /** * Hide this panel (on clicking the database button to open the database * chooser) */ void hidePanel() { 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 * choice has been made), and a Back button is made visible (which reopens the * Sequence Fetcher panel). * * @param parentPanel */ public void embedIn(GFTSPanel parentPanel) { database.setVisible(false); backBtn.setVisible(true); parentSearchPanel = parentPanel; } }