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