JAL-3949 Complete new abstracted logging framework in jalview.log. Updated log calls...
[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.datamodel.AlignmentI;
41 import jalview.datamodel.DBRefEntry;
42 import jalview.datamodel.DBRefSource;
43 import jalview.datamodel.Mapping;
44 import jalview.datamodel.SequenceI;
45 import jalview.gui.CutAndPasteTransfer;
46 import jalview.gui.Desktop;
47 import jalview.gui.FeatureSettings;
48 import jalview.gui.IProgressIndicator;
49 import jalview.gui.OOMWarning;
50 import jalview.util.DBRefUtils;
51 import jalview.util.MessageManager;
52 import jalview.ws.seqfetcher.DbSourceProxy;
53 import uk.ac.ebi.picr.model.UPEntry;
54 import uk.ac.ebi.www.picr.AccessionMappingService.AccessionMapperServiceLocator;
55
56 /**
57  * Implements a runnable for validating a sequence against external databases
58  * and then propagating references and features onto the sequence(s)
59  * 
60  * @author $author$
61  * @version $Revision$
62  */
63 public class DBRefFetcher implements Runnable
64 {
65   private static final String NEWLINE = System.lineSeparator();
66
67   public static final String TRIM_RETRIEVED_SEQUENCES = "TRIM_FETCHED_DATASET_SEQS";
68
69   public interface FetchFinishedListenerI
70   {
71     void finished();
72   }
73
74   SequenceI[] dataset;
75
76   IProgressIndicator progressWindow;
77
78   CutAndPasteTransfer output = new CutAndPasteTransfer();
79
80   /**
81    * picr client instance
82    */
83   uk.ac.ebi.www.picr.AccessionMappingService.AccessionMapperInterface picrClient = null;
84
85   // This will be a collection of Vectors of sequenceI refs.
86   // The key will be the seq name or accession id of the seq
87   Hashtable<String, Vector<SequenceI>> seqRefs;
88
89   DbSourceProxy[] dbSources;
90
91   SequenceFetcher sfetcher;
92
93   private List<FetchFinishedListenerI> listeners;
94
95   private SequenceI[] alseqs;
96
97   /*
98    * when true - retrieved sequences will be trimmed to cover longest derived
99    * alignment sequence
100    */
101   private boolean trimDsSeqs = true;
102
103   /**
104    * Creates a new DBRefFetcher object and fetches from the currently selected
105    * set of databases, if this is null then it fetches based on feature settings
106    * 
107    * @param seqs
108    *          fetch references for these SequenceI array
109    * @param progressIndicatorFrame
110    *          the frame for progress bar monitoring
111    * @param sources
112    *          array of DbSourceProxy to query references form
113    * @param featureSettings
114    *          FeatureSettings to get alternative DbSourceProxy from
115    * @param isNucleotide
116    *          indicates if the array of SequenceI are Nucleotides or not
117    */
118   public DBRefFetcher(SequenceI[] seqs,
119           IProgressIndicator progressIndicatorFrame,
120           DbSourceProxy[] sources, FeatureSettings featureSettings,
121           boolean isNucleotide)
122   {
123     listeners = new ArrayList<>();
124     this.progressWindow = progressIndicatorFrame;
125     alseqs = new SequenceI[seqs.length];
126     SequenceI[] ds = new SequenceI[seqs.length];
127     for (int i = 0; i < seqs.length; i++)
128     {
129       alseqs[i] = seqs[i];
130       if (seqs[i].getDatasetSequence() != null)
131       {
132         ds[i] = seqs[i].getDatasetSequence();
133       }
134       else
135       {
136         ds[i] = seqs[i];
137       }
138     }
139     this.dataset = ds;
140     // TODO Jalview 2.5 lots of this code should be in the gui package!
141     sfetcher = jalview.gui.SequenceFetcher.getSequenceFetcherSingleton();
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     if (waitTillFinished)
230     {
231       run();
232     }
233     else
234     {
235       new Thread(this).start();
236     }
237   }
238
239   /**
240    * The sequence will be added to a vector of sequences belonging to key which
241    * could be either seq name or dbref id
242    * 
243    * @param seq
244    *          SequenceI
245    * @param key
246    *          String
247    */
248   void addSeqId(SequenceI seq, String key)
249   {
250     key = key.toUpperCase(Locale.ROOT);
251
252     Vector<SequenceI> seqs;
253     if (seqRefs.containsKey(key))
254     {
255       seqs = seqRefs.get(key);
256
257       if (seqs != null && !seqs.contains(seq))
258       {
259         seqs.addElement(seq);
260       }
261       else if (seqs == null)
262       {
263         seqs = new Vector<>();
264         seqs.addElement(seq);
265       }
266
267     }
268     else
269     {
270       seqs = new Vector<>();
271       seqs.addElement(seq);
272     }
273
274     seqRefs.put(key, seqs);
275   }
276
277   /**
278    * DOCUMENT ME!
279    */
280   @Override
281   public void run()
282   {
283     if (dbSources == null)
284     {
285       throw new Error(MessageManager
286               .getString("error.implementation_error_must_init_dbsources"));
287     }
288     long startTime = System.currentTimeMillis();
289     if (progressWindow != null)
290     {
291       progressWindow.setProgressBar(
292               MessageManager.getString("status.fetching_db_refs"),
293               startTime);
294     }
295     try
296     {
297       if (Cache.getDefault("DBREFFETCH_USEPICR", false))
298       {
299         picrClient = new AccessionMapperServiceLocator()
300                 .getAccessionMapperPort();
301       }
302     } catch (Exception e)
303     {
304       System.err.println("Couldn't locate PICR service instance.\n");
305       e.printStackTrace();
306     }
307
308     Vector<SequenceI> sdataset = new Vector<>(
309             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 (Cache.isDebugEnabled())
366             {
367               Cache.debug("Querying " + dbsource.getDbName()
368                       + " with : '" + 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,
382                     trimDsSeqs, 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,
513           AlignmentI retrievedAl, boolean trimDatasetSeqs,
514           List<String> warningMessages)
515   {
516     // System.out.println("trimming ? " + trimDatasetSeqs);
517     if (retrievedAl == null || retrievedAl.getHeight() == 0)
518     {
519       return false;
520     }
521
522     String dbSource = dbSourceProxy.getDbName();
523     boolean modified = false;
524     SequenceI[] retrieved = recoverDbSequences(
525             retrievedAl.getSequencesArray());
526     SequenceI sequence = null;
527
528     for (SequenceI retrievedSeq : retrieved)
529     {
530       // Work out which sequences this sequence matches,
531       // taking into account all accessionIds and names in the file
532       Vector<SequenceI> sequenceMatches = new Vector<>();
533       // look for corresponding accession ids
534       List<DBRefEntry> entryRefs = DBRefUtils
535               .selectRefs(retrievedSeq.getDBRefs(), new String[]
536               { dbSource });
537       if (entryRefs == null)
538       {
539         System.err
540                 .println("Dud dbSource string ? no entryrefs selected for "
541                         + dbSource + " on " + retrievedSeq.getName());
542         continue;
543       }
544       for (int j = 0, n = entryRefs.size(); j < n; j++)
545       {
546         DBRefEntry ref = entryRefs.get(j);
547         String accessionId = ref.getAccessionId();
548         // match up on accessionId
549         if (seqRefs.containsKey(accessionId.toUpperCase(Locale.ROOT)))
550         {
551           Vector<SequenceI> seqs = seqRefs.get(accessionId);
552           for (int jj = 0; jj < seqs.size(); jj++)
553           {
554             sequence = seqs.elementAt(jj);
555             if (!sequenceMatches.contains(sequence))
556             {
557               sequenceMatches.addElement(sequence);
558             }
559           }
560         }
561       }
562       if (sequenceMatches.isEmpty())
563       {
564         // failed to match directly on accessionId==query so just compare all
565         // sequences to entry
566         Enumeration<String> e = seqRefs.keys();
567         while (e.hasMoreElements())
568         {
569           Vector<SequenceI> sqs = seqRefs.get(e.nextElement());
570           if (sqs != null && sqs.size() > 0)
571           {
572             Enumeration<SequenceI> sqe = sqs.elements();
573             while (sqe.hasMoreElements())
574             {
575               sequenceMatches.addElement(sqe.nextElement());
576             }
577           }
578         }
579       }
580       // look for corresponding names
581       // this is uniprot specific ?
582       // could be useful to extend this so we try to find any 'significant'
583       // information in common between two sequence objects.
584       /*
585        * List<DBRefEntry> entryRefs =
586        * jalview.util.DBRefUtils.selectRefs(entry.getDBRef(), new String[] {
587        * dbSource }); for (int j = 0; j < entry.getName().size(); j++) { String
588        * name = entry.getName().elementAt(j).toString(); if
589        * (seqRefs.containsKey(name)) { Vector seqs = (Vector) seqRefs.get(name);
590        * for (int jj = 0; jj < seqs.size(); jj++) { sequence = (SequenceI)
591        * seqs.elementAt(jj); if (!sequenceMatches.contains(sequence)) {
592        * sequenceMatches.addElement(sequence); } } } }
593        */
594       if (sequenceMatches.size() > 0)
595       {
596         addFeatureSettings(dbSourceProxy);
597       }
598       // sequenceMatches now contains the set of all sequences associated with
599       // the returned db record
600       final String retrievedSeqString = retrievedSeq.getSequenceAsString();
601       String entrySeq = retrievedSeqString.toUpperCase(Locale.ROOT);
602       for (int m = 0; m < sequenceMatches.size(); m++)
603       {
604         sequence = sequenceMatches.elementAt(m);
605         // only update start and end positions and shift features if there are
606         // no existing references
607         // TODO: test for legacy where uniprot or EMBL refs exist but no
608         // mappings are made (but content matches retrieved set)
609         boolean updateRefFrame = sequence.getDBRefs() == null
610                 || sequence.getDBRefs().size() == 0;
611         // TODO:
612         // verify sequence against the entry sequence
613
614         Mapping mp;
615         final int sequenceStart = sequence.getStart();
616
617         boolean remoteEnclosesLocal = false;
618         String nonGapped = AlignSeq
619                 .extractGaps("-. ", sequence.getSequenceAsString())
620                 .toUpperCase(Locale.ROOT);
621         int absStart = entrySeq.indexOf(nonGapped);
622         if (absStart == -1)
623         {
624           // couldn't find local sequence in sequence from database, so check if
625           // the database sequence is a subsequence of local sequence
626           absStart = nonGapped.indexOf(entrySeq);
627           if (absStart == -1)
628           {
629             // verification failed. couldn't find any relationship between
630             // entrySeq and local sequence
631             // messages suppressed as many-to-many matches are confusing
632             // String msg = sequence.getName()
633             // + " Sequence not 100% match with "
634             // + retrievedSeq.getName();
635             // addWarningMessage(warningMessages, msg);
636             continue;
637           }
638           /*
639            * retrieved sequence is a proper subsequence of local sequence
640            */
641           String msg = sequence.getName() + " has " + absStart
642                   + " prefixed residues compared to "
643                   + retrievedSeq.getName();
644           addWarningMessage(warningMessages, msg);
645
646           /*
647            * So create a mapping to the external entry from the matching region of 
648            * the local sequence, and leave local start/end untouched. 
649            */
650           mp = new Mapping(null,
651                   new int[]
652                   { sequenceStart + absStart,
653                       sequenceStart + absStart + entrySeq.length() - 1 },
654                   new int[]
655                   { retrievedSeq.getStart(),
656                       retrievedSeq.getStart() + entrySeq.length() - 1 },
657                   1, 1);
658           updateRefFrame = false;
659         }
660         else
661         {
662           /*
663            * local sequence is a subsequence of (or matches) retrieved sequence
664            */
665           remoteEnclosesLocal = true;
666           mp = null;
667
668           if (updateRefFrame)
669           {
670             /*
671              * relocate existing sequence features by offset
672              */
673             int startShift = absStart - sequenceStart + 1;
674             if (startShift != 0)
675             {
676               modified |= sequence.getFeatures().shiftFeatures(1,
677                       startShift);
678             }
679           }
680         }
681
682         System.out.println("Adding dbrefs to " + sequence.getName()
683                 + " from " + dbSource + " sequence : "
684                 + retrievedSeq.getName());
685         sequence.transferAnnotation(retrievedSeq, mp);
686
687         absStart += retrievedSeq.getStart();
688         int absEnd = absStart + nonGapped.length() - 1;
689         if (!trimDatasetSeqs)
690         {
691           /*
692            * update start position and/or expand to longer retrieved sequence
693            */
694           if (!retrievedSeqString.equals(sequence.getSequenceAsString())
695                   && remoteEnclosesLocal)
696           {
697             sequence.setSequence(retrievedSeqString);
698             modified = true;
699             addWarningMessage(warningMessages,
700                     "Sequence for " + sequence.getName() + " expanded from "
701                             + retrievedSeq.getName());
702           }
703           if (sequence.getStart() != retrievedSeq.getStart())
704           {
705             sequence.setStart(retrievedSeq.getStart());
706             modified = true;
707             if (absStart != sequenceStart)
708             {
709               addWarningMessage(warningMessages,
710                       "Start/end position for " + sequence.getName()
711                               + " updated from " + retrievedSeq.getName());
712             }
713           }
714         }
715         if (updateRefFrame)
716         {
717           // finally, update local sequence reference frame if we're allowed
718           if (trimDatasetSeqs)
719           {
720             // just fix start/end
721             if (sequence.getStart() != absStart
722                     || sequence.getEnd() != absEnd)
723             {
724               sequence.setStart(absStart);
725               sequence.setEnd(absEnd);
726               modified = true;
727               addWarningMessage(warningMessages,
728                       "Start/end for " + sequence.getName()
729                               + " updated from " + retrievedSeq.getName());
730             }
731           }
732           // search for alignment sequences to update coordinate frame for
733           for (int alsq = 0; alsq < alseqs.length; alsq++)
734           {
735             if (alseqs[alsq].getDatasetSequence() == sequence)
736             {
737               String ngAlsq = AlignSeq
738                       .extractGaps("-. ",
739                               alseqs[alsq].getSequenceAsString())
740                       .toUpperCase(Locale.ROOT);
741               int oldstrt = alseqs[alsq].getStart();
742               alseqs[alsq].setStart(sequence.getSequenceAsString()
743                       .toUpperCase(Locale.ROOT).indexOf(ngAlsq) + 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 sequences
785    */
786   public List<FeatureSettingsModelI>getFeatureSettingsModels()
787   {
788     return featureDisplaySettings == null
789             ? Arrays.asList(new FeatureSettingsModelI[0])
790             : Arrays.asList(featureDisplaySettings.values()
791                     .toArray(new FeatureSettingsModelI[1]));
792   }
793   /**
794    * Adds the message to the list unless it already contains it
795    * 
796    * @param messageList
797    * @param msg
798    */
799   void addWarningMessage(List<String> messageList, String msg)
800   {
801     if (!messageList.contains(msg))
802     {
803       messageList.add(msg);
804     }
805   }
806
807   /**
808    * loop thru and collect additional sequences in Map.
809    * 
810    * @param sequencesArray
811    * @return
812    */
813   private SequenceI[] recoverDbSequences(SequenceI[] sequencesArray)
814   {
815         int n;
816         if (sequencesArray == null || (n = sequencesArray.length) == 0)
817           return sequencesArray;
818     ArrayList<SequenceI> nseq = new ArrayList<>();
819     for (int i = 0;i < n; i++)
820     {
821       nseq.add(sequencesArray[i]);
822       List<DBRefEntry> dbr = sequencesArray[i].getDBRefs();
823       Mapping map = null;
824       if (dbr != null)
825       {
826         for (int r = 0, rn = dbr.size(); r < rn; r++)
827         {
828           if ((map = dbr.get(r).getMap()) != null)
829           {
830             if (map.getTo() != null && !nseq.contains(map.getTo()))
831             {
832               nseq.add(map.getTo());
833             }
834           }  
835         }
836       }
837     }
838     // BH 2019.01.25 question here if this is the right logic. Return the original if nothing found?
839     if (nseq.size() > 0)
840     {
841       return nseq.toArray(new SequenceI[nseq.size()]);
842     }
843     return sequencesArray;
844   }
845 }