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