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