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