JAL-3608 Changes from code review CR-JAL-236
[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.getSequenceFetcherSingleton();
141     // set default behaviour for transferring excess sequence data to the
142     // dataset
143     trimDsSeqs = Cache.getDefault(TRIM_RETRIEVED_SEQUENCES, true);
144     if (sources == null)
145     {
146       setDatabaseSources(featureSettings, isNucleotide);
147     }
148     else
149     {
150       // we assume the caller knows what they're doing and ensured that all the
151       // db source names are valid
152       dbSources = sources;
153     }
154   }
155
156   /**
157    * Helper method to configure the list of database sources to query
158    * 
159    * @param featureSettings
160    * @param forNucleotide
161    */
162   void setDatabaseSources(FeatureSettings featureSettings,
163           boolean forNucleotide)
164   {
165     // af.featureSettings_actionPerformed(null);
166     String[] defdb = null;
167     List<DbSourceProxy> selsources = new ArrayList<>();
168     // select appropriate databases based on alignFrame context.
169     if (forNucleotide)
170     {
171       defdb = DBRefSource.DNACODINGDBS;
172     }
173     else
174     {
175       defdb = DBRefSource.PROTEINDBS;
176     }
177     List<DbSourceProxy> srces = new ArrayList<>();
178     for (String ddb : defdb)
179     {
180       List<DbSourceProxy> srcesfordb = sfetcher.getSourceProxy(ddb);
181       if (srcesfordb != null)
182       {
183         for (DbSourceProxy src : srcesfordb)
184         {
185           if (!srces.contains(src))
186           {
187             srces.addAll(srcesfordb);
188           }
189         }
190       }
191     }
192     // append the PDB data source, since it is 'special', catering for both
193     // nucleotide and protein
194     // srces.addAll(sfetcher.getSourceProxy(DBRefSource.PDB));
195
196     srces.addAll(selsources);
197     dbSources = srces.toArray(new DbSourceProxy[srces.size()]);
198   }
199
200   /**
201    * Constructor with only sequences provided
202    * 
203    * @param sequences
204    */
205   public DBRefFetcher(SequenceI[] sequences)
206   {
207     this(sequences, null, null, null, false);
208   }
209
210   /**
211    * Add a listener to be notified when sequence fetching is complete
212    * 
213    * @param l
214    */
215   public void addListener(FetchFinishedListenerI l)
216   {
217     listeners.add(l);
218   }
219
220   /**
221    * start the fetcher thread
222    * 
223    * @param waitTillFinished
224    *          true to block until the fetcher has finished
225    */
226   public void fetchDBRefs(boolean waitTillFinished)
227   {
228     // TODO can we not simply write
229     // if (waitTillFinished) { run(); } else { new Thread(this).start(); }
230
231     Thread thread = new Thread(this);
232     thread.start();
233     running = true;
234
235     if (waitTillFinished)
236     {
237       while (running)
238       {
239         try
240         {
241           Thread.sleep(500);
242         } catch (Exception ex)
243         {
244         }
245       }
246     }
247   }
248
249   /**
250    * The sequence will be added to a vector of sequences belonging to key which
251    * could be either seq name or dbref id
252    * 
253    * @param seq
254    *          SequenceI
255    * @param key
256    *          String
257    */
258   void addSeqId(SequenceI seq, String key)
259   {
260     key = key.toUpperCase();
261
262     Vector<SequenceI> seqs;
263     if (seqRefs.containsKey(key))
264     {
265       seqs = seqRefs.get(key);
266
267       if (seqs != null && !seqs.contains(seq))
268       {
269         seqs.addElement(seq);
270       }
271       else if (seqs == null)
272       {
273         seqs = new Vector<>();
274         seqs.addElement(seq);
275       }
276
277     }
278     else
279     {
280       seqs = new Vector<>();
281       seqs.addElement(seq);
282     }
283
284     seqRefs.put(key, seqs);
285   }
286
287   /**
288    * DOCUMENT ME!
289    */
290   @Override
291   public void run()
292   {
293     if (dbSources == null)
294     {
295       throw new Error(MessageManager
296               .getString("error.implementation_error_must_init_dbsources"));
297     }
298     running = true;
299     long startTime = System.currentTimeMillis();
300     if (progressWindow != null)
301     {
302       progressWindow.setProgressBar(
303               MessageManager.getString("status.fetching_db_refs"),
304               startTime);
305     }
306     try
307     {
308       if (Cache.getDefault("DBREFFETCH_USEPICR", false))
309       {
310         picrClient = new AccessionMapperServiceLocator()
311                 .getAccessionMapperPort();
312       }
313     } catch (Exception e)
314     {
315       System.err.println("Couldn't locate PICR service instance.\n");
316       e.printStackTrace();
317     }
318
319     Vector<SequenceI> sdataset = new Vector<>(
320             Arrays.asList(dataset));
321     List<String> warningMessages = new ArrayList<>();
322
323     // clear any old feature display settings recorded from past sessions
324     featureDisplaySettings = null;
325
326     int db = 0;
327     while (sdataset.size() > 0 && db < dbSources.length)
328     {
329       int maxqlen = 1; // default number of queries made at one time
330       System.out.println("Verifying against " + dbSources[db].getDbName());
331
332       // iterate through db for each remaining un-verified sequence
333       SequenceI[] currSeqs = new SequenceI[sdataset.size()];
334       sdataset.copyInto(currSeqs);// seqs that are to be validated against
335       // dbSources[db]
336       Vector<String> queries = new Vector<>(); // generated queries curSeq
337       seqRefs = new Hashtable<>();
338
339       int seqIndex = 0;
340
341       DbSourceProxy dbsource = dbSources[db];
342       // for moment, we dumbly iterate over all retrieval sources for a
343       // particular database
344       // TODO: introduce multithread multisource queries and logic to remove a
345       // query from other sources if any source for a database returns a
346       // record
347       maxqlen = dbsource.getMaximumQueryCount();
348
349       while (queries.size() > 0 || seqIndex < currSeqs.length)
350       {
351         if (queries.size() > 0)
352         {
353           // Still queries to make for current seqIndex
354           StringBuffer queryString = new StringBuffer("");
355           int numq = 0;
356           int nqSize = (maxqlen > queries.size()) ? queries.size()
357                   : maxqlen;
358
359           while (queries.size() > 0 && numq < nqSize)
360           {
361             String query = queries.elementAt(0);
362             if (dbsource.isValidReference(query))
363             {
364               queryString.append(
365                       (numq == 0) ? "" : dbsource.getAccessionSeparator());
366               queryString.append(query);
367               numq++;
368             }
369             // remove the extracted query string
370             queries.removeElementAt(0);
371           }
372           // make the queries and process the response
373           AlignmentI retrieved = null;
374           try
375           {
376             if (Cache.log.isDebugEnabled())
377             {
378               Cache.log.debug("Querying " + dbsource.getDbName()
379                       + " with : '" + queryString.toString() + "'");
380             }
381             retrieved = dbsource.getSequenceRecords(queryString.toString());
382           } catch (Exception ex)
383           {
384             ex.printStackTrace();
385           } catch (OutOfMemoryError err)
386           {
387             new OOMWarning("retrieving database references ("
388                     + queryString.toString() + ")", err);
389           }
390           if (retrieved != null)
391           {
392             transferReferences(sdataset, dbsource, retrieved,
393                     trimDsSeqs, warningMessages);
394           }
395         }
396         else
397         {
398           // make some more strings for use as queries
399           for (int i = 0; (seqIndex < dataset.length)
400                   && (i < 50); seqIndex++, i++)
401           {
402             SequenceI sequence = dataset[seqIndex];
403             List<DBRefEntry> uprefs = DBRefUtils
404                     .selectRefs(sequence.getDBRefs(), new String[]
405                     { dbsource.getDbSource() }); // jalview.datamodel.DBRefSource.UNIPROT
406             // });
407             // check for existing dbrefs to use
408             if (uprefs != null && uprefs.size() > 0)
409             {
410               for (int j = 0, n = uprefs.size(); j < n; j++)
411               {
412                 DBRefEntry upref = uprefs.get(j);
413                 addSeqId(sequence, upref.getAccessionId());
414                 queries.addElement(
415                         upref.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       List<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, n = entryRefs.size(); j < n; j++)
555       {
556         DBRefEntry ref = entryRefs.get(j);
557         String accessionId = ref.getAccessionId();
558         // match up on accessionId
559         if (seqRefs.containsKey(accessionId.toUpperCase()))
560         {
561           Vector<SequenceI> seqs = seqRefs.get(accessionId);
562           for (int jj = 0; jj < seqs.size(); jj++)
563           {
564             sequence = seqs.elementAt(jj);
565             if (!sequenceMatches.contains(sequence))
566             {
567               sequenceMatches.addElement(sequence);
568             }
569           }
570         }
571       }
572       if (sequenceMatches.isEmpty())
573       {
574         // failed to match directly on accessionId==query so just compare all
575         // sequences to entry
576         Enumeration<String> e = seqRefs.keys();
577         while (e.hasMoreElements())
578         {
579           Vector<SequenceI> sqs = seqRefs.get(e.nextElement());
580           if (sqs != null && sqs.size() > 0)
581           {
582             Enumeration<SequenceI> sqe = sqs.elements();
583             while (sqe.hasMoreElements())
584             {
585               sequenceMatches.addElement(sqe.nextElement());
586             }
587           }
588         }
589       }
590       // look for corresponding names
591       // this is uniprot specific ?
592       // could be useful to extend this so we try to find any 'significant'
593       // information in common between two sequence objects.
594       /*
595        * List<DBRefEntry> entryRefs =
596        * jalview.util.DBRefUtils.selectRefs(entry.getDBRef(), new String[] {
597        * dbSource }); for (int j = 0; j < entry.getName().size(); j++) { String
598        * name = entry.getName().elementAt(j).toString(); if
599        * (seqRefs.containsKey(name)) { Vector seqs = (Vector) seqRefs.get(name);
600        * for (int jj = 0; jj < seqs.size(); jj++) { sequence = (SequenceI)
601        * seqs.elementAt(jj); if (!sequenceMatches.contains(sequence)) {
602        * sequenceMatches.addElement(sequence); } } } }
603        */
604       if (sequenceMatches.size() > 0)
605       {
606         addFeatureSettings(dbSourceProxy);
607       }
608       // sequenceMatches now contains the set of all sequences associated with
609       // the returned db record
610       final String retrievedSeqString = retrievedSeq.getSequenceAsString();
611       String entrySeq = retrievedSeqString.toUpperCase();
612       for (int m = 0; m < sequenceMatches.size(); m++)
613       {
614         sequence = sequenceMatches.elementAt(m);
615         // only update start and end positions and shift features if there are
616         // no existing references
617         // TODO: test for legacy where uniprot or EMBL refs exist but no
618         // mappings are made (but content matches retrieved set)
619         boolean updateRefFrame = sequence.getDBRefs() == null
620                 || sequence.getDBRefs().size() == 0;
621         // TODO:
622         // verify sequence against the entry sequence
623
624         Mapping mp;
625         final int sequenceStart = sequence.getStart();
626
627         boolean remoteEnclosesLocal = false;
628         String nonGapped = AlignSeq
629                 .extractGaps("-. ", sequence.getSequenceAsString())
630                 .toUpperCase();
631         int absStart = entrySeq.indexOf(nonGapped);
632         if (absStart == -1)
633         {
634           // couldn't find local sequence in sequence from database, so check if
635           // the database sequence is a subsequence of local sequence
636           absStart = nonGapped.indexOf(entrySeq);
637           if (absStart == -1)
638           {
639             // verification failed. couldn't find any relationship between
640             // entrySeq and local sequence
641             // messages suppressed as many-to-many matches are confusing
642             // String msg = sequence.getName()
643             // + " Sequence not 100% match with "
644             // + retrievedSeq.getName();
645             // addWarningMessage(warningMessages, msg);
646             continue;
647           }
648           /*
649            * retrieved sequence is a proper subsequence of local sequence
650            */
651           String msg = sequence.getName() + " has " + absStart
652                   + " prefixed residues compared to "
653                   + retrievedSeq.getName();
654           addWarningMessage(warningMessages, msg);
655
656           /*
657            * So create a mapping to the external entry from the matching region of 
658            * the local sequence, and leave local start/end untouched. 
659            */
660           mp = new Mapping(null,
661                   new int[]
662                   { sequenceStart + absStart,
663                       sequenceStart + absStart + entrySeq.length() - 1 },
664                   new int[]
665                   { retrievedSeq.getStart(),
666                       retrievedSeq.getStart() + entrySeq.length() - 1 },
667                   1, 1);
668           updateRefFrame = false;
669         }
670         else
671         {
672           /*
673            * local sequence is a subsequence of (or matches) retrieved sequence
674            */
675           remoteEnclosesLocal = true;
676           mp = null;
677
678           if (updateRefFrame)
679           {
680             /*
681              * relocate existing sequence features by offset
682              */
683             int startShift = absStart - sequenceStart + 1;
684             if (startShift != 0)
685             {
686               modified |= sequence.getFeatures().shiftFeatures(1,
687                       startShift);
688             }
689           }
690         }
691
692         System.out.println("Adding dbrefs to " + sequence.getName()
693                 + " from " + dbSource + " sequence : "
694                 + retrievedSeq.getName());
695         sequence.transferAnnotation(retrievedSeq, mp);
696
697         absStart += retrievedSeq.getStart();
698         int absEnd = absStart + nonGapped.length() - 1;
699         if (!trimDatasetSeqs)
700         {
701           /*
702            * update start position and/or expand to longer retrieved sequence
703            */
704           if (!retrievedSeqString.equals(sequence.getSequenceAsString())
705                   && remoteEnclosesLocal)
706           {
707             sequence.setSequence(retrievedSeqString);
708             modified = true;
709             addWarningMessage(warningMessages,
710                     "Sequence for " + sequence.getName() + " expanded from "
711                             + retrievedSeq.getName());
712           }
713           if (sequence.getStart() != retrievedSeq.getStart())
714           {
715             sequence.setStart(retrievedSeq.getStart());
716             modified = true;
717             if (absStart != sequenceStart)
718             {
719               addWarningMessage(warningMessages,
720                       "Start/end position for " + sequence.getName()
721                               + " updated from " + retrievedSeq.getName());
722             }
723           }
724         }
725         if (updateRefFrame)
726         {
727           // finally, update local sequence reference frame if we're allowed
728           if (trimDatasetSeqs)
729           {
730             // just fix start/end
731             if (sequence.getStart() != absStart
732                     || sequence.getEnd() != absEnd)
733             {
734               sequence.setStart(absStart);
735               sequence.setEnd(absEnd);
736               modified = true;
737               addWarningMessage(warningMessages,
738                       "Start/end for " + sequence.getName()
739                               + " updated from " + retrievedSeq.getName());
740             }
741           }
742           // search for alignment sequences to update coordinate frame for
743           for (int alsq = 0; alsq < alseqs.length; alsq++)
744           {
745             if (alseqs[alsq].getDatasetSequence() == sequence)
746             {
747               String ngAlsq = AlignSeq
748                       .extractGaps("-. ",
749                               alseqs[alsq].getSequenceAsString())
750                       .toUpperCase();
751               int oldstrt = alseqs[alsq].getStart();
752               alseqs[alsq].setStart(sequence.getSequenceAsString()
753                       .toUpperCase().indexOf(ngAlsq) + sequence.getStart());
754               if (oldstrt != alseqs[alsq].getStart())
755               {
756                 alseqs[alsq].setEnd(
757                         ngAlsq.length() + alseqs[alsq].getStart() - 1);
758                 modified = true;
759               }
760             }
761           }
762           // TODO: search for all other references to this dataset sequence, and
763           // update start/end
764           // TODO: update all AlCodonMappings which involve this alignment
765           // sequence (e.g. Q30167 cdna translation from exon2 product (vamsas
766           // demo)
767         }
768         // and remove it from the rest
769         // TODO: decide if we should remove annotated sequence from set
770         sdataset.remove(sequence);
771       }
772     }
773     return modified;
774   }
775
776   Map<String, FeatureSettingsModelI> featureDisplaySettings = null;
777
778   private void addFeatureSettings(DbSourceProxy dbSourceProxy)
779   {
780     FeatureSettingsModelI fsettings = dbSourceProxy
781             .getFeatureColourScheme();
782     if (fsettings != null)
783     {
784       if (featureDisplaySettings == null)
785       {
786         featureDisplaySettings = new HashMap<>();
787       }
788       featureDisplaySettings.put(dbSourceProxy.getDbName(), fsettings);
789     }
790   }
791
792   /**
793    * 
794    * @return any feature settings associated with sources that have provided sequences
795    */
796   public List<FeatureSettingsModelI>getFeatureSettingsModels()
797   {
798     return featureDisplaySettings == null
799             ? Arrays.asList(new FeatureSettingsModelI[0])
800             : Arrays.asList(featureDisplaySettings.values()
801                     .toArray(new FeatureSettingsModelI[1]));
802   }
803   /**
804    * Adds the message to the list unless it already contains it
805    * 
806    * @param messageList
807    * @param msg
808    */
809   void addWarningMessage(List<String> messageList, String msg)
810   {
811     if (!messageList.contains(msg))
812     {
813       messageList.add(msg);
814     }
815   }
816
817   /**
818    * loop thru and collect additional sequences in Map.
819    * 
820    * @param sequencesArray
821    * @return
822    */
823   private SequenceI[] recoverDbSequences(SequenceI[] sequencesArray)
824   {
825         int n;
826         if (sequencesArray == null || (n = sequencesArray.length) == 0)
827           return sequencesArray;
828     ArrayList<SequenceI> nseq = new ArrayList<>();
829     for (int i = 0;i < n; i++)
830     {
831       nseq.add(sequencesArray[i]);
832       List<DBRefEntry> dbr = sequencesArray[i].getDBRefs();
833       Mapping map = null;
834       if (dbr != null)
835       {
836         for (int r = 0, rn = dbr.size(); r < rn; r++)
837         {
838           if ((map = dbr.get(r).getMap()) != null)
839           {
840             if (map.getTo() != null && !nseq.contains(map.getTo()))
841             {
842               nseq.add(map.getTo());
843             }
844           }  
845         }
846       }
847     }
848     // BH 2019.01.25 question here if this is the right logic. Return the original if nothing found?
849     if (nseq.size() > 0)
850     {
851       return nseq.toArray(new SequenceI[nseq.size()]);
852     }
853     return sequencesArray;
854   }
855 }