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