JAL-3463 avoid Thread.sleep() as not supported in SwingJS
[jalview.git] / src / jalview / ws / DBRefFetcher.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.ws;
22
23 import java.util.ArrayList;
24 import java.util.Arrays;
25 import java.util.Enumeration;
26 import java.util.HashMap;
27 import java.util.Hashtable;
28 import java.util.List;
29 import java.util.Map;
30 import java.util.StringTokenizer;
31 import java.util.Vector;
32
33 import jalview.analysis.AlignSeq;
34 import jalview.api.FeatureSettingsModelI;
35 import jalview.bin.Cache;
36 import jalview.datamodel.AlignmentI;
37 import jalview.datamodel.DBRefEntry;
38 import jalview.datamodel.DBRefSource;
39 import jalview.datamodel.Mapping;
40 import jalview.datamodel.SequenceI;
41 import jalview.gui.CutAndPasteTransfer;
42 import jalview.gui.Desktop;
43 import jalview.gui.FeatureSettings;
44 import jalview.gui.IProgressIndicator;
45 import jalview.gui.OOMWarning;
46 import jalview.util.DBRefUtils;
47 import jalview.util.MessageManager;
48 import jalview.ws.seqfetcher.DbSourceProxy;
49 import uk.ac.ebi.picr.model.UPEntry;
50 import uk.ac.ebi.www.picr.AccessionMappingService.AccessionMapperServiceLocator;
51
52 /**
53  * Implements a runnable for validating a sequence against external databases
54  * and then propagating references and features onto the sequence(s)
55  * 
56  * @author $author$
57  * @version $Revision$
58  */
59 public class DBRefFetcher implements Runnable
60 {
61   private static final String NEWLINE = System.lineSeparator();
62
63   public static final String TRIM_RETRIEVED_SEQUENCES = "TRIM_FETCHED_DATASET_SEQS";
64
65   public interface FetchFinishedListenerI
66   {
67     void finished();
68   }
69
70   SequenceI[] dataset;
71
72   IProgressIndicator progressWindow;
73
74   CutAndPasteTransfer output = new CutAndPasteTransfer();
75
76   /**
77    * picr client instance
78    */
79   uk.ac.ebi.www.picr.AccessionMappingService.AccessionMapperInterface picrClient = null;
80
81   // This will be a collection of Vectors of sequenceI refs.
82   // The key will be the seq name or accession id of the seq
83   Hashtable<String, Vector<SequenceI>> seqRefs;
84
85   DbSourceProxy[] dbSources;
86
87   SequenceFetcher sfetcher;
88
89   private List<FetchFinishedListenerI> listeners;
90
91   private SequenceI[] alseqs;
92
93   /*
94    * when true - retrieved sequences will be trimmed to cover longest derived
95    * alignment sequence
96    */
97   private boolean trimDsSeqs = true;
98
99   /**
100    * Creates a new DBRefFetcher object and fetches from the currently selected
101    * set of databases, if this is null then it fetches based on feature settings
102    * 
103    * @param seqs
104    *          fetch references for these SequenceI array
105    * @param progressIndicatorFrame
106    *          the frame for progress bar monitoring
107    * @param sources
108    *          array of DbSourceProxy to query references form
109    * @param featureSettings
110    *          FeatureSettings to get alternative DbSourceProxy from
111    * @param isNucleotide
112    *          indicates if the array of SequenceI are Nucleotides or not
113    */
114   public DBRefFetcher(SequenceI[] seqs,
115           IProgressIndicator progressIndicatorFrame,
116           DbSourceProxy[] sources, FeatureSettings featureSettings,
117           boolean isNucleotide)
118   {
119     listeners = new ArrayList<>();
120     this.progressWindow = progressIndicatorFrame;
121     alseqs = new SequenceI[seqs.length];
122     SequenceI[] ds = new SequenceI[seqs.length];
123     for (int i = 0; i < seqs.length; i++)
124     {
125       alseqs[i] = seqs[i];
126       if (seqs[i].getDatasetSequence() != null)
127       {
128         ds[i] = seqs[i].getDatasetSequence();
129       }
130       else
131       {
132         ds[i] = seqs[i];
133       }
134     }
135     this.dataset = ds;
136     // TODO Jalview 2.5 lots of this code should be in the gui package!
137     sfetcher = jalview.gui.SequenceFetcher.getSequenceFetcherSingleton();
138     // set default behaviour for transferring excess sequence data to the
139     // dataset
140     trimDsSeqs = Cache.getDefault(TRIM_RETRIEVED_SEQUENCES, true);
141     if (sources == null)
142     {
143       setDatabaseSources(featureSettings, isNucleotide);
144     }
145     else
146     {
147       // we assume the caller knows what they're doing and ensured that all the
148       // db source names are valid
149       dbSources = sources;
150     }
151   }
152
153   /**
154    * Helper method to configure the list of database sources to query
155    * 
156    * @param featureSettings
157    * @param forNucleotide
158    */
159   void setDatabaseSources(FeatureSettings featureSettings,
160           boolean forNucleotide)
161   {
162     // af.featureSettings_actionPerformed(null);
163     String[] defdb = null;
164     List<DbSourceProxy> selsources = new ArrayList<>();
165     // select appropriate databases based on alignFrame context.
166     if (forNucleotide)
167     {
168       defdb = DBRefSource.DNACODINGDBS;
169     }
170     else
171     {
172       defdb = DBRefSource.PROTEINDBS;
173     }
174     List<DbSourceProxy> srces = new ArrayList<>();
175     for (String ddb : defdb)
176     {
177       List<DbSourceProxy> srcesfordb = sfetcher.getSourceProxy(ddb);
178       if (srcesfordb != null)
179       {
180         for (DbSourceProxy src : srcesfordb)
181         {
182           if (!srces.contains(src))
183           {
184             srces.addAll(srcesfordb);
185           }
186         }
187       }
188     }
189     // append the PDB data source, since it is 'special', catering for both
190     // nucleotide and protein
191     // srces.addAll(sfetcher.getSourceProxy(DBRefSource.PDB));
192
193     srces.addAll(selsources);
194     dbSources = srces.toArray(new DbSourceProxy[srces.size()]);
195   }
196
197   /**
198    * Constructor with only sequences provided
199    * 
200    * @param sequences
201    */
202   public DBRefFetcher(SequenceI[] sequences)
203   {
204     this(sequences, null, null, null, false);
205   }
206
207   /**
208    * Add a listener to be notified when sequence fetching is complete
209    * 
210    * @param l
211    */
212   public void addListener(FetchFinishedListenerI l)
213   {
214     listeners.add(l);
215   }
216
217   /**
218    * start the fetcher thread
219    * 
220    * @param waitTillFinished
221    *          true to block until the fetcher has finished
222    */
223   public void fetchDBRefs(boolean waitTillFinished)
224   {
225     if (waitTillFinished)
226     {
227       run();
228     }
229     else
230     {
231       new Thread(this).start();
232     }
233   }
234
235   /**
236    * The sequence will be added to a vector of sequences belonging to key which
237    * could be either seq name or dbref id
238    * 
239    * @param seq
240    *          SequenceI
241    * @param key
242    *          String
243    */
244   void addSeqId(SequenceI seq, String key)
245   {
246     key = key.toUpperCase();
247
248     Vector<SequenceI> seqs;
249     if (seqRefs.containsKey(key))
250     {
251       seqs = seqRefs.get(key);
252
253       if (seqs != null && !seqs.contains(seq))
254       {
255         seqs.addElement(seq);
256       }
257       else if (seqs == null)
258       {
259         seqs = new Vector<>();
260         seqs.addElement(seq);
261       }
262
263     }
264     else
265     {
266       seqs = new Vector<>();
267       seqs.addElement(seq);
268     }
269
270     seqRefs.put(key, seqs);
271   }
272
273   /**
274    * DOCUMENT ME!
275    */
276   @Override
277   public void run()
278   {
279     if (dbSources == null)
280     {
281       throw new Error(MessageManager
282               .getString("error.implementation_error_must_init_dbsources"));
283     }
284     long startTime = System.currentTimeMillis();
285     if (progressWindow != null)
286     {
287       progressWindow.setProgressBar(
288               MessageManager.getString("status.fetching_db_refs"),
289               startTime);
290     }
291     try
292     {
293       if (Cache.getDefault("DBREFFETCH_USEPICR", false))
294       {
295         picrClient = new AccessionMapperServiceLocator()
296                 .getAccessionMapperPort();
297       }
298     } catch (Exception e)
299     {
300       System.err.println("Couldn't locate PICR service instance.\n");
301       e.printStackTrace();
302     }
303
304     Vector<SequenceI> sdataset = new Vector<>(
305             Arrays.asList(dataset));
306     List<String> warningMessages = new ArrayList<>();
307
308     // clear any old feature display settings recorded from past sessions
309     featureDisplaySettings = null;
310
311     int db = 0;
312     while (sdataset.size() > 0 && db < dbSources.length)
313     {
314       int maxqlen = 1; // default number of queries made at one time
315       System.out.println("Verifying against " + dbSources[db].getDbName());
316
317       // iterate through db for each remaining un-verified sequence
318       SequenceI[] currSeqs = new SequenceI[sdataset.size()];
319       sdataset.copyInto(currSeqs);// seqs that are to be validated against
320       // dbSources[db]
321       Vector<String> queries = new Vector<>(); // generated queries curSeq
322       seqRefs = new Hashtable<>();
323
324       int seqIndex = 0;
325
326       DbSourceProxy dbsource = dbSources[db];
327       // for moment, we dumbly iterate over all retrieval sources for a
328       // particular database
329       // TODO: introduce multithread multisource queries and logic to remove a
330       // query from other sources if any source for a database returns a
331       // record
332       maxqlen = dbsource.getMaximumQueryCount();
333
334       while (queries.size() > 0 || seqIndex < currSeqs.length)
335       {
336         if (queries.size() > 0)
337         {
338           // Still queries to make for current seqIndex
339           StringBuffer queryString = new StringBuffer("");
340           int numq = 0;
341           int nqSize = (maxqlen > queries.size()) ? queries.size()
342                   : maxqlen;
343
344           while (queries.size() > 0 && numq < nqSize)
345           {
346             String query = queries.elementAt(0);
347             if (dbsource.isValidReference(query))
348             {
349               queryString.append(
350                       (numq == 0) ? "" : dbsource.getAccessionSeparator());
351               queryString.append(query);
352               numq++;
353             }
354             // remove the extracted query string
355             queries.removeElementAt(0);
356           }
357           // make the queries and process the response
358           AlignmentI retrieved = null;
359           try
360           {
361             if (Cache.log.isDebugEnabled())
362             {
363               Cache.log.debug("Querying " + dbsource.getDbName()
364                       + " with : '" + queryString.toString() + "'");
365             }
366             retrieved = dbsource.getSequenceRecords(queryString.toString());
367           } catch (Exception ex)
368           {
369             ex.printStackTrace();
370           } catch (OutOfMemoryError err)
371           {
372             new OOMWarning("retrieving database references ("
373                     + queryString.toString() + ")", err);
374           }
375           if (retrieved != null)
376           {
377             transferReferences(sdataset, dbsource, retrieved,
378                     trimDsSeqs, warningMessages);
379           }
380         }
381         else
382         {
383           // make some more strings for use as queries
384           for (int i = 0; (seqIndex < dataset.length)
385                   && (i < 50); seqIndex++, i++)
386           {
387             SequenceI sequence = dataset[seqIndex];
388             List<DBRefEntry> uprefs = DBRefUtils
389                     .selectRefs(sequence.getDBRefs(), new String[]
390                     { dbsource.getDbSource() }); // jalview.datamodel.DBRefSource.UNIPROT
391             // });
392             // check for existing dbrefs to use
393             if (uprefs != null && uprefs.size() > 0)
394             {
395               for (int j = 0, n = uprefs.size(); j < n; j++)
396               {
397                 DBRefEntry upref = uprefs.get(j);
398                 addSeqId(sequence, upref.getAccessionId());
399                 queries.addElement(
400                         upref.getAccessionId().toUpperCase());
401               }
402             }
403             else
404             {
405               // generate queries from sequence ID string
406               StringTokenizer st = new StringTokenizer(sequence.getName(),
407                       "|");
408               while (st.hasMoreTokens())
409               {
410                 String token = st.nextToken();
411                 UPEntry[] presp = null;
412                 if (picrClient != null)
413                 {
414                   // resolve the string against PICR to recover valid IDs
415                   try
416                   {
417                     presp = picrClient.getUPIForAccession(token, null,
418                             picrClient.getMappedDatabaseNames(), null,
419                             true);
420                   } catch (Exception e)
421                   {
422                     System.err.println(
423                             "Exception with Picr for '" + token + "'\n");
424                     e.printStackTrace();
425                   }
426                 }
427                 if (presp != null && presp.length > 0)
428                 {
429                   for (int id = 0; id < presp.length; id++)
430                   {
431                     // construct sequences from response if sequences are
432                     // present, and do a transferReferences
433                     // otherwise transfer non sequence x-references directly.
434                   }
435                   System.out.println(
436                           "Validated ID against PICR... (for what its worth):"
437                                   + token);
438                   addSeqId(sequence, token);
439                   queries.addElement(token.toUpperCase());
440                 }
441                 else
442                 {
443                   // if ()
444                   // System.out.println("Not querying source with
445                   // token="+token+"\n");
446                   addSeqId(sequence, token);
447                   queries.addElement(token.toUpperCase());
448                 }
449               }
450             }
451           }
452         }
453       }
454       // advance to next database
455       db++;
456     } // all databases have been queried
457     if (!warningMessages.isEmpty())
458     {
459       StringBuilder sb = new StringBuilder(warningMessages.size() * 30);
460       sb.append(MessageManager
461               .getString("label.your_sequences_have_been_verified"));
462       for (String msg : warningMessages)
463       {
464         sb.append(msg).append(NEWLINE);
465       }
466       output.setText(sb.toString());
467
468       Desktop.addInternalFrame(output,
469               MessageManager.getString("label.sequences_updated"), 600,
470               300);
471       // The above is the dataset, we must now find out the index
472       // of the viewed sequence
473
474     }
475     if (progressWindow != null)
476     {
477       progressWindow.setProgressBar(
478               MessageManager.getString("label.dbref_search_completed"),
479               startTime);
480     }
481
482     for (FetchFinishedListenerI listener : listeners)
483     {
484       listener.finished();
485     }
486   }
487
488   /**
489    * Verify local sequences in seqRefs against the retrieved sequence database
490    * records. Returns true if any sequence was modified as a result (start/end
491    * changed and/or sequence enlarged), else false.
492    * 
493    * @param sdataset
494    *          dataset sequences we are retrieving for
495    * @param dbSource
496    *          database source we are retrieving from
497    * @param retrievedAl
498    *          retrieved sequences as alignment
499    * @param trimDatasetSeqs
500    *          if true, sequences will not be enlarged to match longer retrieved
501    *          sequences, only their start/end adjusted
502    * @param warningMessages
503    *          a list of messages to add to
504    */
505   boolean transferReferences(Vector<SequenceI> sdataset,
506           DbSourceProxy dbSourceProxy,
507           AlignmentI retrievedAl, boolean trimDatasetSeqs,
508           List<String> warningMessages)
509   {
510     // System.out.println("trimming ? " + trimDatasetSeqs);
511     if (retrievedAl == null || retrievedAl.getHeight() == 0)
512     {
513       return false;
514     }
515
516     String dbSource = dbSourceProxy.getDbName();
517     boolean modified = false;
518     SequenceI[] retrieved = recoverDbSequences(
519             retrievedAl.getSequencesArray());
520     SequenceI sequence = null;
521
522     for (SequenceI retrievedSeq : retrieved)
523     {
524       // Work out which sequences this sequence matches,
525       // taking into account all accessionIds and names in the file
526       Vector<SequenceI> sequenceMatches = new Vector<>();
527       // look for corresponding accession ids
528       List<DBRefEntry> entryRefs = DBRefUtils
529               .selectRefs(retrievedSeq.getDBRefs(), new String[]
530               { dbSource });
531       if (entryRefs == null)
532       {
533         System.err
534                 .println("Dud dbSource string ? no entryrefs selected for "
535                         + dbSource + " on " + retrievedSeq.getName());
536         continue;
537       }
538       for (int j = 0, n = entryRefs.size(); j < n; j++)
539       {
540         DBRefEntry ref = entryRefs.get(j);
541         String accessionId = ref.getAccessionId();
542         // match up on accessionId
543         if (seqRefs.containsKey(accessionId.toUpperCase()))
544         {
545           Vector<SequenceI> seqs = seqRefs.get(accessionId);
546           for (int jj = 0; jj < seqs.size(); jj++)
547           {
548             sequence = seqs.elementAt(jj);
549             if (!sequenceMatches.contains(sequence))
550             {
551               sequenceMatches.addElement(sequence);
552             }
553           }
554         }
555       }
556       if (sequenceMatches.isEmpty())
557       {
558         // failed to match directly on accessionId==query so just compare all
559         // sequences to entry
560         Enumeration<String> e = seqRefs.keys();
561         while (e.hasMoreElements())
562         {
563           Vector<SequenceI> sqs = seqRefs.get(e.nextElement());
564           if (sqs != null && sqs.size() > 0)
565           {
566             Enumeration<SequenceI> sqe = sqs.elements();
567             while (sqe.hasMoreElements())
568             {
569               sequenceMatches.addElement(sqe.nextElement());
570             }
571           }
572         }
573       }
574       // look for corresponding names
575       // this is uniprot specific ?
576       // could be useful to extend this so we try to find any 'significant'
577       // information in common between two sequence objects.
578       /*
579        * List<DBRefEntry> entryRefs =
580        * jalview.util.DBRefUtils.selectRefs(entry.getDBRef(), new String[] {
581        * dbSource }); for (int j = 0; j < entry.getName().size(); j++) { String
582        * name = entry.getName().elementAt(j).toString(); if
583        * (seqRefs.containsKey(name)) { Vector seqs = (Vector) seqRefs.get(name);
584        * for (int jj = 0; jj < seqs.size(); jj++) { sequence = (SequenceI)
585        * seqs.elementAt(jj); if (!sequenceMatches.contains(sequence)) {
586        * sequenceMatches.addElement(sequence); } } } }
587        */
588       if (sequenceMatches.size() > 0)
589       {
590         addFeatureSettings(dbSourceProxy);
591       }
592       // sequenceMatches now contains the set of all sequences associated with
593       // the returned db record
594       final String retrievedSeqString = retrievedSeq.getSequenceAsString();
595       String entrySeq = retrievedSeqString.toUpperCase();
596       for (int m = 0; m < sequenceMatches.size(); m++)
597       {
598         sequence = sequenceMatches.elementAt(m);
599         // only update start and end positions and shift features if there are
600         // no existing references
601         // TODO: test for legacy where uniprot or EMBL refs exist but no
602         // mappings are made (but content matches retrieved set)
603         boolean updateRefFrame = sequence.getDBRefs() == null
604                 || sequence.getDBRefs().size() == 0;
605         // TODO:
606         // verify sequence against the entry sequence
607
608         Mapping mp;
609         final int sequenceStart = sequence.getStart();
610
611         boolean remoteEnclosesLocal = false;
612         String nonGapped = AlignSeq
613                 .extractGaps("-. ", sequence.getSequenceAsString())
614                 .toUpperCase();
615         int absStart = entrySeq.indexOf(nonGapped);
616         if (absStart == -1)
617         {
618           // couldn't find local sequence in sequence from database, so check if
619           // the database sequence is a subsequence of local sequence
620           absStart = nonGapped.indexOf(entrySeq);
621           if (absStart == -1)
622           {
623             // verification failed. couldn't find any relationship between
624             // entrySeq and local sequence
625             // messages suppressed as many-to-many matches are confusing
626             // String msg = sequence.getName()
627             // + " Sequence not 100% match with "
628             // + retrievedSeq.getName();
629             // addWarningMessage(warningMessages, msg);
630             continue;
631           }
632           /*
633            * retrieved sequence is a proper subsequence of local sequence
634            */
635           String msg = sequence.getName() + " has " + absStart
636                   + " prefixed residues compared to "
637                   + retrievedSeq.getName();
638           addWarningMessage(warningMessages, msg);
639
640           /*
641            * So create a mapping to the external entry from the matching region of 
642            * the local sequence, and leave local start/end untouched. 
643            */
644           mp = new Mapping(null,
645                   new int[]
646                   { sequenceStart + absStart,
647                       sequenceStart + absStart + entrySeq.length() - 1 },
648                   new int[]
649                   { retrievedSeq.getStart(),
650                       retrievedSeq.getStart() + entrySeq.length() - 1 },
651                   1, 1);
652           updateRefFrame = false;
653         }
654         else
655         {
656           /*
657            * local sequence is a subsequence of (or matches) retrieved sequence
658            */
659           remoteEnclosesLocal = true;
660           mp = null;
661
662           if (updateRefFrame)
663           {
664             /*
665              * relocate existing sequence features by offset
666              */
667             int startShift = absStart - sequenceStart + 1;
668             if (startShift != 0)
669             {
670               modified |= sequence.getFeatures().shiftFeatures(1,
671                       startShift);
672             }
673           }
674         }
675
676         System.out.println("Adding dbrefs to " + sequence.getName()
677                 + " from " + dbSource + " sequence : "
678                 + retrievedSeq.getName());
679         sequence.transferAnnotation(retrievedSeq, mp);
680
681         absStart += retrievedSeq.getStart();
682         int absEnd = absStart + nonGapped.length() - 1;
683         if (!trimDatasetSeqs)
684         {
685           /*
686            * update start position and/or expand to longer retrieved sequence
687            */
688           if (!retrievedSeqString.equals(sequence.getSequenceAsString())
689                   && remoteEnclosesLocal)
690           {
691             sequence.setSequence(retrievedSeqString);
692             modified = true;
693             addWarningMessage(warningMessages,
694                     "Sequence for " + sequence.getName() + " expanded from "
695                             + retrievedSeq.getName());
696           }
697           if (sequence.getStart() != retrievedSeq.getStart())
698           {
699             sequence.setStart(retrievedSeq.getStart());
700             modified = true;
701             if (absStart != sequenceStart)
702             {
703               addWarningMessage(warningMessages,
704                       "Start/end position for " + sequence.getName()
705                               + " updated from " + retrievedSeq.getName());
706             }
707           }
708         }
709         if (updateRefFrame)
710         {
711           // finally, update local sequence reference frame if we're allowed
712           if (trimDatasetSeqs)
713           {
714             // just fix start/end
715             if (sequence.getStart() != absStart
716                     || sequence.getEnd() != absEnd)
717             {
718               sequence.setStart(absStart);
719               sequence.setEnd(absEnd);
720               modified = true;
721               addWarningMessage(warningMessages,
722                       "Start/end for " + sequence.getName()
723                               + " updated from " + retrievedSeq.getName());
724             }
725           }
726           // search for alignment sequences to update coordinate frame for
727           for (int alsq = 0; alsq < alseqs.length; alsq++)
728           {
729             if (alseqs[alsq].getDatasetSequence() == sequence)
730             {
731               String ngAlsq = AlignSeq
732                       .extractGaps("-. ",
733                               alseqs[alsq].getSequenceAsString())
734                       .toUpperCase();
735               int oldstrt = alseqs[alsq].getStart();
736               alseqs[alsq].setStart(sequence.getSequenceAsString()
737                       .toUpperCase().indexOf(ngAlsq) + sequence.getStart());
738               if (oldstrt != alseqs[alsq].getStart())
739               {
740                 alseqs[alsq].setEnd(
741                         ngAlsq.length() + alseqs[alsq].getStart() - 1);
742                 modified = true;
743               }
744             }
745           }
746           // TODO: search for all other references to this dataset sequence, and
747           // update start/end
748           // TODO: update all AlCodonMappings which involve this alignment
749           // sequence (e.g. Q30167 cdna translation from exon2 product (vamsas
750           // demo)
751         }
752         // and remove it from the rest
753         // TODO: decide if we should remove annotated sequence from set
754         sdataset.remove(sequence);
755       }
756     }
757     return modified;
758   }
759
760   Map<String, FeatureSettingsModelI> featureDisplaySettings = null;
761
762   private void addFeatureSettings(DbSourceProxy dbSourceProxy)
763   {
764     FeatureSettingsModelI fsettings = dbSourceProxy
765             .getFeatureColourScheme();
766     if (fsettings != null)
767     {
768       if (featureDisplaySettings == null)
769       {
770         featureDisplaySettings = new HashMap<>();
771       }
772       featureDisplaySettings.put(dbSourceProxy.getDbName(), fsettings);
773     }
774   }
775
776   /**
777    * 
778    * @return any feature settings associated with sources that have provided sequences
779    */
780   public List<FeatureSettingsModelI>getFeatureSettingsModels()
781   {
782     return featureDisplaySettings == null
783             ? Arrays.asList(new FeatureSettingsModelI[0])
784             : Arrays.asList(featureDisplaySettings.values()
785                     .toArray(new FeatureSettingsModelI[1]));
786   }
787   /**
788    * Adds the message to the list unless it already contains it
789    * 
790    * @param messageList
791    * @param msg
792    */
793   void addWarningMessage(List<String> messageList, String msg)
794   {
795     if (!messageList.contains(msg))
796     {
797       messageList.add(msg);
798     }
799   }
800
801   /**
802    * loop thru and collect additional sequences in Map.
803    * 
804    * @param sequencesArray
805    * @return
806    */
807   private SequenceI[] recoverDbSequences(SequenceI[] sequencesArray)
808   {
809         int n;
810         if (sequencesArray == null || (n = sequencesArray.length) == 0)
811           return sequencesArray;
812     ArrayList<SequenceI> nseq = new ArrayList<>();
813     for (int i = 0;i < n; i++)
814     {
815       nseq.add(sequencesArray[i]);
816       List<DBRefEntry> dbr = sequencesArray[i].getDBRefs();
817       Mapping map = null;
818       if (dbr != null)
819       {
820         for (int r = 0, rn = dbr.size(); r < rn; r++)
821         {
822           if ((map = dbr.get(r).getMap()) != null)
823           {
824             if (map.getTo() != null && !nseq.contains(map.getTo()))
825             {
826               nseq.add(map.getTo());
827             }
828           }  
829         }
830       }
831     }
832     // BH 2019.01.25 question here if this is the right logic. Return the original if nothing found?
833     if (nseq.size() > 0)
834     {
835       return nseq.toArray(new SequenceI[nseq.size()]);
836     }
837     return sequencesArray;
838   }
839 }