AsyncFetchTask for SequenceFetcher
[jalview.git] / src / jalview / gui / SequenceFetcher.java
1 /*
2  * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
3  * Copyright (C) $$Year-Rel$$ The Jalview Authors
4  * 
5  * This file is part of Jalview.
6  * 
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.
11  *  
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.
16  * 
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.
20  */
21 package jalview.gui;
22
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;
37
38 import java.awt.BorderLayout;
39 import java.awt.Component;
40 import java.awt.Font;
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;
50
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;
60
61 import javajs.async.AsyncSwingWorker;
62
63 /**
64  * A panel where the use may choose a database source, and enter one or more
65  * accessions, to retrieve entries from the database.
66  * <p>
67  * If the selected source is Uniprot or PDB, a free text search panel is opened
68  * instead to perform the search and selection.
69  */
70 @SuppressWarnings("serial")
71 public class SequenceFetcher extends JPanel
72 {
73
74   JLabel exampleAccession;
75
76   JComboBox<String> database;
77
78   JCheckBox replacePunctuation;
79
80   JButton okBtn;
81
82   JButton exampleBtn;
83
84   JButton closeBtn;
85
86   JButton backBtn;
87
88   JTextArea textArea;
89
90   JInternalFrame frame;
91
92   IProgressIndicator guiWindow;
93
94   AlignFrame alignFrame;
95
96   GFTSPanel parentSearchPanel;
97
98   IProgressIndicator progressIndicator;
99
100   volatile boolean _isConstructing = false;
101
102   /**
103    * Constructor given a client to receive any status or progress messages
104    * (currently either the Desktop, or an AlignFrame panel)
105    * 
106    * @param guiIndic
107    */
108   public SequenceFetcher(IProgressIndicator guiIndic)
109   {
110     this(guiIndic, null, null);
111   }
112
113   /**
114    * Constructor with specified database and accession(s) to retrieve
115    * 
116    * @param guiIndic
117    * @param selectedDb
118    * @param queryString
119    */
120   public SequenceFetcher(IProgressIndicator guiIndic,
121           final String selectedDb, final String queryString)
122   {
123     this.progressIndicator = guiIndic;
124
125     this.guiWindow = progressIndicator;
126
127     if (progressIndicator instanceof AlignFrame)
128     {
129       alignFrame = (AlignFrame) progressIndicator;
130     }
131
132     jbInit(selectedDb);
133     textArea.setText(queryString);
134
135     frame = new JInternalFrame();
136     frame.setContentPane(this);
137     Desktop.addInternalFrame(frame, getFrameTitle(), true, 400,
138             Platform.isAMacAndNotJS() ? 240 : 180);
139   }
140
141   private String getFrameTitle()
142   {
143     return ((alignFrame == null)
144             ? MessageManager.getString("label.new_sequence_fetcher")
145             : MessageManager
146                     .getString("label.additional_sequence_fetcher"));
147   }
148
149   private void jbInit(String selectedDb)
150   {
151     this.setLayout(new BorderLayout());
152
153     database = new JComboBox<>();
154     database.setFont(JvSwingUtils.getLabelFont());
155     database.setPrototypeDisplayValue("ENSEMBLGENOMES   ");
156     String[] sources = jalview.ws.SequenceFetcher.getInstance()
157             .getSupportedDb();
158     Arrays.sort(sources, String.CASE_INSENSITIVE_ORDER);
159     database.addItem(MessageManager.getString("action.select_ddbb"));
160     for (String source : sources)
161     {
162       database.addItem(source);
163     }
164     database.setSelectedItem(selectedDb);
165     if (database.getSelectedIndex() == -1)
166     {
167       database.setSelectedIndex(0);
168     }
169     database.setMaximumRowCount(database.getItemCount());
170     database.addActionListener(new ActionListener()
171     {
172       @Override
173       public void actionPerformed(ActionEvent e)
174       {
175         String currentSelection = (String) database.getSelectedItem();
176         updateExampleQuery(currentSelection);
177
178         if ("pdb".equalsIgnoreCase(currentSelection))
179         {
180           frame.dispose();
181           new PDBFTSPanel(SequenceFetcher.this);
182         }
183         else if ("uniprot".equalsIgnoreCase(currentSelection))
184         {
185           frame.dispose();
186           new UniprotFTSPanel(SequenceFetcher.this);
187         }
188         else
189         {
190           otherSourceAction();
191         }
192       }
193     });
194
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);
201
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()
208     {
209       @Override
210       public void actionPerformed(ActionEvent e)
211       {
212         ok_actionPerformed();
213       }
214     });
215     JButton clear = new JButton(MessageManager.getString("action.clear"));
216     clear.addActionListener(new ActionListener()
217     {
218       @Override
219       public void actionPerformed(ActionEvent e)
220       {
221         clear_actionPerformed();
222       }
223     });
224
225     exampleBtn = new JButton(MessageManager.getString("label.example"));
226     exampleBtn.addActionListener(new ActionListener()
227     {
228       @Override
229       public void actionPerformed(ActionEvent e)
230       {
231         example_actionPerformed();
232       }
233     });
234     closeBtn = new JButton(MessageManager.getString("action.cancel"));
235     closeBtn.addActionListener(new ActionListener()
236     {
237       @Override
238       public void actionPerformed(ActionEvent e)
239       {
240         close_actionPerformed(e);
241       }
242     });
243     backBtn = new JButton(MessageManager.getString("action.back"));
244     backBtn.addActionListener(new ActionListener()
245     {
246       @Override
247       public void actionPerformed(ActionEvent e)
248       {
249         parentSearchPanel.btn_back_ActionPerformed();
250       }
251     });
252     // back not visible unless embedded
253     backBtn.setVisible(false);
254
255     textArea = new JTextArea();
256     textArea.setFont(JvSwingUtils.getLabelFont());
257     textArea.setLineWrap(true);
258     textArea.addKeyListener(new KeyAdapter()
259     {
260       @Override
261       public void keyPressed(KeyEvent e)
262       {
263         if (e.getKeyCode() == KeyEvent.VK_ENTER)
264         {
265           ok_actionPerformed();
266         }
267       }
268     });
269
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);
276
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);
285
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);
291
292     this.add(actionPanel, BorderLayout.SOUTH);
293     this.add(idsPanel, BorderLayout.CENTER);
294     this.add(databasePanel, BorderLayout.NORTH);
295   }
296
297   /**
298    * Answers a semi-colon-delimited string with the example query or queries for
299    * the selected database
300    * 
301    * @param db
302    * @return
303    */
304   protected String getExampleQueries(String db)
305   {
306     StringBuilder sb = new StringBuilder();
307     HashSet<String> hs = new HashSet<>();
308     for (DbSourceProxy dbs : jalview.ws.SequenceFetcher.getInstance()
309             .getSourceProxy(db))
310     {
311       String tq = dbs.getTestQuery();
312       if (hs.add(tq)) // not a duplicate source
313       {
314         if (sb.length() > 0)
315         {
316           sb.append(";");
317         }
318         sb.append(tq);
319       }
320     }
321     return sb.toString();
322   }
323
324   /**
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
327    */
328   protected void otherSourceAction()
329   {
330     try
331     {
332       String eq = exampleAccession.getText();
333       // TODO this should be a property of the SequenceFetcher whether commas
334       // are allowed in the IDs...
335
336       boolean enablePunct = !(eq != null && eq.indexOf(",") > -1);
337       replacePunctuation.setEnabled(enablePunct);
338
339     } catch (Exception ex)
340     {
341       exampleAccession.setText("");
342       replacePunctuation.setEnabled(true);
343     }
344     repaint();
345   }
346
347   /**
348    * Sets the text of the example query to incorporate the example accession
349    * provided by the selected database source
350    * 
351    * @param selectedDatabase
352    * @return
353    */
354   protected String updateExampleQuery(String selectedDatabase)
355   {
356     String eq = getExampleQueries(selectedDatabase);
357     exampleAccession.setText(MessageManager
358             .formatMessage("label.example_query_param", new String[]
359             { eq }));
360     return eq;
361   }
362
363   /**
364    * Action on clicking the 'Example' button is to write the example accession
365    * as the query text field value
366    */
367   protected void example_actionPerformed()
368   {
369     String eq = getExampleQueries((String) database.getSelectedItem());
370     textArea.setText(eq);
371     repaint();
372   }
373
374   /**
375    * Clears the query input field
376    */
377   protected void clear_actionPerformed()
378   {
379     textArea.setText("");
380     repaint();
381   }
382
383   /**
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
386    * 
387    * @param e
388    */
389   protected void close_actionPerformed(ActionEvent e)
390   {
391     try
392     {
393       frame.setClosed(true);
394       if (parentSearchPanel != null)
395       {
396         parentSearchPanel.btn_cancel_ActionPerformed();
397       }
398     } catch (Exception ex)
399     {
400     }
401   }
402
403   /**
404    * Action on OK is to start the fetch for entered accession(s)
405    */
406   public void ok_actionPerformed()
407   {
408     /*
409      * tidy inputs and check there is something to search for
410      */
411     String t0 = textArea.getText();
412     String text = t0.trim();
413     if (replacePunctuation.isEnabled() && replacePunctuation.isSelected())
414     {
415       text = text.replace(",", ";");
416     }
417     text = text.replaceAll("(\\s|[; ])+", ";");
418     if (!t0.equals(text))
419     {
420       textArea.setText(text);
421     }
422     if (text.isEmpty())
423     {
424       // todo i18n
425       showErrorMessage(
426               "Please enter a (semi-colon separated list of) database id(s)");
427       resetDialog();
428       return;
429     }
430     exampleBtn.setEnabled(false);
431     textArea.setEnabled(false);
432     okBtn.setEnabled(false);
433     closeBtn.setEnabled(false);
434     backBtn.setEnabled(false);
435     fetch(null, true);
436   }
437
438   public void fetch(String ids, boolean isAsync)
439   {
440     if (ids == null)
441     {
442       ids = textArea.getText();
443     }
444     else
445     {
446       textArea.setText(ids);
447     }
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();
454   }
455
456
457   protected void resetDialog()
458   {
459     exampleBtn.setEnabled(true);
460     textArea.setEnabled(true);
461     okBtn.setEnabled(true);
462     closeBtn.setEnabled(true);
463     backBtn.setEnabled(parentSearchPanel != null);
464   }
465
466   /**
467    * This asynchronous class allows for a single-threaded state machine
468    * SwingWorker to process a list of requests from multiple sources.
469    * 
470    * A standard ProcessMonitor could be attached to this task, but it is
471    * currently not.
472    * 
473    * @author hansonr
474    *
475    */
476   private class AsyncFetchTask extends AsyncSwingWorker
477   {
478
479     protected boolean addToLast = false;
480
481     protected List<String> aresultq = new ArrayList<>();
482
483     protected List<String> presultTitle = new ArrayList<>();
484
485     protected List<AlignmentI> presult = new ArrayList<>();
486
487     protected List<AlignmentI> aresult = new ArrayList<>();
488
489     protected FeatureSettingsModelI preferredFeatureColours = null;
490
491     protected List<DbSourceProxy> sources;
492
493     protected Iterator<DbSourceProxy> sourceIterator;
494
495     protected String[] fetchArray;
496
497     protected List<String> fetchList;
498
499     protected Iterator<String> fetchIterator;
500
501     protected int fetchCount;
502
503     protected DbSourceProxy source;
504
505     protected String ids;
506
507     protected long myID;
508
509     protected boolean isAsync;
510
511     protected Throwable taskError;
512
513     public AsyncFetchTask(String ids, Component owner, String title,
514             int delayMillis,
515             int min, int max)
516     {
517       super(owner, title, delayMillis, min, max);
518       this.ids = ids;
519       isAsync = (delayMillis != 0);
520       myID = Thread.currentThread().getId();
521     }
522
523     @Override
524     public void initAsync()
525     {
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);
531     }
532
533     private final static int STATE_INIT = 0;
534
535     private final static int STATE_NEXT_SOURCE = 10;
536
537     private final static int STATE_FETCH_SINGLE = 30;
538
539     private final static int STATE_FETCH_MULTIPLE = 40;
540
541     private final static int STATE_PROCESS = 50;
542
543     private final static int STATE_PARSE_RESULTS = 80;
544
545     private final static int STATE_DONE = 100;
546
547     protected static final int STATE_TASK_ERROR = 99;
548
549     @Override
550     public int doInBackgroundAsync(int progress)
551     {
552       System.out.println("SequenceFetcher.AsyncFetchTask " + isAsync + " "
553               + progress + " "
554               + taskError);
555       switch (progress)
556       {
557       case STATE_INIT:
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)
563         {
564           showProgress((presult.size() > 0)
565                   ? MessageManager.getString("status.parsing_results")
566                   : MessageManager.getString("status.processing"));
567           return STATE_PARSE_RESULTS;
568         }
569         source = sourceIterator.next();
570         if (doneFetching)
571         {
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]);
577           // and clear the
578           fetchList = new ArrayList<>();
579         }
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())
588         {
589           return runSubtask(new Runnable()
590           {
591
592             @Override
593             public void run()
594             {
595               // source only handles one accession id at a time
596               try
597               {
598                 if (!isAsync)
599                 {
600                   // for CrossRef2xmlTest only; to "allow the server to breathe"
601                   // (but we are doing that already when isAsync is true)
602                   Thread.sleep(5);
603                 }
604                 String notFound = fetchSingleAccession(source,
605                         fetchIterator.next(), aresultq, aresult);
606                 if (notFound != null)
607                 {
608                   fetchList.add(notFound);
609                 }
610               } catch (Throwable e)
611               {
612                 taskError = e;
613               }
614             }
615           });
616         }
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()
622         {
623
624           @Override
625           public void run()
626           {
627             try
628             {
629               fetchMultipleAccessions(source, fetchIterator, aresultq,
630                       aresult, fetchList);
631             } catch (Throwable e)
632             {
633               taskError = e;
634             }
635           }
636
637         });
638       case STATE_PROCESS:
639         // Stack results ready for opening in alignment windows
640         if (aresult != null && aresult.size() > 0)
641         {
642           FeatureSettingsModelI proxyColourScheme = source
643                   .getFeatureColourScheme();
644           if (proxyColourScheme != null)
645           {
646             preferredFeatureColours = proxyColourScheme;
647           }
648
649           AlignmentI ar = null;
650           if (source.isAlignmentSource())
651           {
652             addToLast = false;
653             // new window for each result
654             while (aresult.size() > 0)
655             {
656               presult.add(aresult.remove(0));
657               presultTitle.add(aresultq.remove(0) + " "
658                       + getDefaultRetrievalTitle());
659             }
660           }
661           else
662           {
663             String titl = null;
664             if (addToLast && presult.size() > 0)
665             {
666               ar = presult.remove(presult.size() - 1);
667               titl = presultTitle.remove(presultTitle.size() - 1);
668             }
669             // concatenate all results in one window
670             while (aresult.size() > 0)
671             {
672               if (ar == null)
673               {
674                 ar = aresult.remove(0);
675               }
676               else
677               {
678                 ar.append(aresult.remove(0));
679               }
680             }
681             addToLast = true;
682             presult.add(ar);
683             presultTitle.add(titl);
684           }
685         }
686         showProgress(MessageManager.getString("status.finshed_querying"));
687         return STATE_NEXT_SOURCE;
688       case STATE_PARSE_RESULTS:
689         while (presult.size() > 0)
690         {
691           parseResult(presult.remove(0), presultTitle.remove(0), null,
692                   preferredFeatureColours);
693         }
694         break;
695       case STATE_TASK_ERROR:
696         showProgress(null);
697         super.cancelAsync();
698         return 0; // arbitrary; ignored
699       }
700       return STATE_DONE;
701     }
702
703     protected void resume()
704     {
705       if (!isAsync)
706       {
707         return;
708       }
709       super.setPaused(false);
710       super.stateLoop();
711     }
712
713     private int runSubtask(Runnable subtask)
714     {
715
716       Runnable r = new Runnable()
717       {
718
719         @Override
720         public void run()
721         {
722           try
723           {
724             subtask.run();
725           } catch (Throwable e)
726           {
727             taskError = e;
728           }
729           if (checkError(taskError))
730           {
731             setProgressAsync(STATE_TASK_ERROR);
732           }
733           else
734           {
735             taskError = null;
736           }
737           if (isAsync)
738           {
739             resume();
740           }
741         }
742       };
743
744       if (isAsync)
745       {
746         super.setPaused(true);
747         new Thread(r).start();
748         return 0; // arbitrary return -- ignored because we are paused.
749       }
750       r.run();
751       return super.getProgressAsync();
752     }
753
754     @Override
755     public void doneAsync()
756     {
757       showProgress(null);
758       if (fetchList.size() > 0)
759       {
760         StringBuffer sb = new StringBuffer();
761         sb.append("Didn't retrieve the following "
762                 + (fetchList.size() == 1 ? "query"
763                         : fetchList.size() + " queries")
764                 + ": \n");
765         int l = sb.length(), lr = 0;
766         for (String s : fetchList)
767         {
768           if (l != sb.length())
769           {
770             sb.append("; ");
771           }
772           if (lr - sb.length() > 40)
773           {
774             sb.append("\n");
775           }
776           sb.append(s);
777         }
778         showErrorMessage(sb.toString());
779       }
780       resetDialog();
781     }
782
783     /**
784      * 
785      * @param e
786      * @return true if there was an error and we need to STOP
787      */
788     protected boolean checkError(Throwable e)
789     {
790       if (e == null)
791       {
792         return false;
793       }
794       String problem = "retrieving " + ids + " from "
795               + database.getSelectedItem();
796       if (e instanceof Exception)
797       {
798         showErrorMessage("Error " + problem);
799         System.err.println("Retrieval failed for source ='"
800                 + database.getSelectedItem() + "' and query\n'"
801                 + ids + "'\n");
802       }
803       else if (e instanceof OutOfMemoryError)
804       {
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
809         // return false;
810       }
811       else
812       {
813         showErrorMessage("Serious Error " + problem);
814       }
815       e.printStackTrace();
816       return true;
817     }
818
819     private void showProgress(String msg)
820     {
821       guiWindow.setProgressBar(msg, myID);
822     }
823
824   }
825
826   /**
827    * Tries to fetch one or more accession ids from the database proxy
828    * 
829    * @param proxy
830    * @param accessions
831    *          the queries to fetch
832    * @param aresultq
833    *          a successful queries list to add to
834    * @param aresult
835    *          a list of retrieved alignments to add to
836    * @param nextFetch
837    *          failed queries are added to this list
838    * @throws Exception
839    */
840   void fetchMultipleAccessions(DbSourceProxy proxy,
841           Iterator<String> accessions, List<String> aresultq,
842           List<AlignmentI> aresult, List<String> nextFetch) throws Exception
843   {
844     StringBuilder multiacc = new StringBuilder();
845     List<String> tosend = new ArrayList<>();
846     while (accessions.hasNext())
847     {
848       String nel = accessions.next();
849       tosend.add(nel);
850       multiacc.append(nel);
851       if (accessions.hasNext())
852       {
853         multiacc.append(proxy.getAccessionSeparator());
854       }
855     }
856
857     try
858     {
859       String query = multiacc.toString();
860       AlignmentI rslt = proxy.getSequenceRecords(query);
861       if (rslt == null || rslt.getHeight() == 0)
862       {
863         // no results - pass on all queries to next source
864         nextFetch.addAll(tosend);
865       }
866       else
867       {
868         aresultq.add(query);
869         aresult.add(rslt);
870         if (tosend.size() > 1)
871         {
872           checkResultForQueries(rslt, tosend, nextFetch, proxy);
873         }
874       }
875     } catch (OutOfMemoryError oome)
876     {
877       new OOMWarning("fetching " + multiacc + " from "
878               + database.getSelectedItem(), oome, this);
879     }
880   }
881
882   /**
883    * Query for a single accession id via the database proxy
884    * 
885    * @param proxy
886    * @param accession
887    * @param aresultq
888    *          a list of successful queries to add to
889    * @param aresult
890    *          a list of retrieved alignments to add to
891    * @return null if the fetch was successful; the accession if not
892    */
893   String fetchSingleAccession(DbSourceProxy proxy, String accession,
894           List<String> aresultq, List<AlignmentI> aresult) throws Throwable
895   {
896     AlignmentI indres = proxy.getSequenceRecords(accession);
897     if (indres == null)
898     {
899       return accession;
900     }
901     aresultq.add(accession);
902     aresult.add(indres);
903     return null;
904   }
905
906   /**
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.
910    * 
911    * @param rslt
912    * @param queries
913    * @param nextFetch
914    * @param proxy
915    */
916   void checkResultForQueries(AlignmentI rslt, List<String> queries,
917           List<String> nextFetch, DbSourceProxy proxy)
918   {
919     SequenceI[] rs = rslt.getSequencesArray();
920
921     for (String q : queries)
922     {
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++)
931       {
932         if (rs[r] != null)
933         {
934           List<DBRefEntry> found = DBRefUtils.searchRefs(rs[r].getDBRefs(),
935                   accId);
936           if (!found.isEmpty())
937           {
938             rfound = true;
939             break;
940           }
941         }
942       }
943       if (!rfound)
944       {
945         nextFetch.add(q);
946       }
947     }
948   }
949
950   /**
951    * 
952    * @return a standard title for any results retrieved using the currently
953    *         selected source and settings
954    */
955   public String getDefaultRetrievalTitle()
956   {
957     return "Retrieved from " + database.getSelectedItem();
958   }
959
960   AlignmentI parseResult(AlignmentI al, String title,
961           FileFormatI currentFileFormat,
962           FeatureSettingsModelI preferredFeatureColours)
963   {
964
965     if (al != null && al.getHeight() > 0)
966     {
967       if (title == null)
968       {
969         title = getDefaultRetrievalTitle();
970       }
971       if (alignFrame == null)
972       {
973         AlignFrame af = new AlignFrame(al, AlignFrame.DEFAULT_WIDTH,
974                 AlignFrame.DEFAULT_HEIGHT);
975         if (currentFileFormat != null)
976         {
977           af.currentFileFormat = currentFileFormat;
978         }
979
980         List<SequenceI> alsqs = al.getSequences();
981         synchronized (alsqs)
982         {
983           for (SequenceI sq : alsqs)
984           {
985             if (sq.getFeatures().hasFeatures())
986             {
987               af.setShowSeqFeatures(true);
988               break;
989             }
990           }
991         }
992
993         if (preferredFeatureColours != null)
994         {
995           af.getViewport().applyFeaturesStyle(preferredFeatureColours);
996         }
997         if (Cache.getDefault(Preferences.HIDE_INTRONS, true))
998         {
999           af.hideFeatureColumns(SequenceOntologyI.EXON, false);
1000         }
1001         Desktop.addInternalFrame(af, title, AlignFrame.DEFAULT_WIDTH,
1002                 AlignFrame.DEFAULT_HEIGHT);
1003
1004         af.setStatus(MessageManager
1005                 .getString("label.successfully_pasted_alignment_file"));
1006
1007         try
1008         {
1009           af.setMaximum(
1010                   Cache.getDefault(Preferences.SHOW_FULLSCREEN, false));
1011         } catch (Exception ex)
1012         {
1013         }
1014       }
1015       else
1016       {
1017         alignFrame.viewport.addAlignment(al, title);
1018       }
1019     }
1020     return al;
1021   }
1022
1023   void showErrorMessage(final String error)
1024   {
1025     resetDialog();
1026     javax.swing.SwingUtilities.invokeLater(new Runnable()
1027     {
1028       @Override
1029       public void run()
1030       {
1031         JvOptionPane.showInternalMessageDialog(Desktop.getDesktopPane(),
1032                 error,
1033                 MessageManager.getString("label.error_retrieving_data"),
1034                 JvOptionPane.WARNING_MESSAGE);
1035       }
1036     });
1037   }
1038
1039   public IProgressIndicator getProgressIndicator()
1040   {
1041     return progressIndicator;
1042   }
1043
1044   public void setProgressIndicator(IProgressIndicator progressIndicator)
1045   {
1046     this.progressIndicator = progressIndicator;
1047   }
1048
1049   /**
1050    * Hide this panel (on clicking the database button to open the database
1051    * chooser)
1052    */
1053   void hidePanel()
1054   {
1055     frame.setVisible(false);
1056   }
1057
1058   /**
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).
1063    * 
1064    * @param parentPanel
1065    */
1066   public void embedIn(GFTSPanel parentPanel)
1067   {
1068     database.setVisible(false);
1069     backBtn.setVisible(true);
1070     parentSearchPanel = parentPanel;
1071   }
1072
1073 }