ae03dfba371848c22e46e0a4bdd131bf4d6a6cb7
[jalview.git] / src / jalview / ws / jws2 / SeqAnnotationServiceCalcWorker.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.jws2;
22
23 import jalview.analysis.AlignSeq;
24 import jalview.analysis.AlignmentAnnotationUtils;
25 import jalview.analysis.SeqsetUtils;
26 import jalview.api.AlignViewportI;
27 import jalview.api.AlignmentViewPanel;
28 import jalview.api.FeatureColourI;
29 import jalview.bin.Cache;
30 import jalview.datamodel.AlignmentAnnotation;
31 import jalview.datamodel.AlignmentI;
32 import jalview.datamodel.AnnotatedCollectionI;
33 import jalview.datamodel.Annotation;
34 import jalview.datamodel.ContiguousI;
35 import jalview.datamodel.Mapping;
36 import jalview.datamodel.SequenceI;
37 import jalview.datamodel.features.FeatureMatcherSetI;
38 import jalview.gui.AlignFrame;
39 import jalview.gui.Desktop;
40 import jalview.gui.IProgressIndicator;
41 import jalview.gui.IProgressIndicatorHandler;
42 import jalview.gui.JvOptionPane;
43 import jalview.gui.WebserviceInfo;
44 import jalview.schemes.FeatureSettingsAdapter;
45 import jalview.schemes.ResidueProperties;
46 import jalview.util.MapList;
47 import jalview.util.MessageManager;
48 import jalview.workers.AlignCalcWorker;
49 import jalview.ws.JobStateSummary;
50 import jalview.ws.api.CancellableI;
51 import jalview.ws.api.JalviewServiceEndpointProviderI;
52 import jalview.ws.api.JobId;
53 import jalview.ws.api.SequenceAnnotationServiceI;
54 import jalview.ws.api.ServiceWithParameters;
55 import jalview.ws.api.WSAnnotationCalcManagerI;
56 import jalview.ws.gui.AnnotationWsJob;
57 import jalview.ws.jws2.dm.AAConSettings;
58 import jalview.ws.params.ArgumentI;
59 import jalview.ws.params.WsParamSetI;
60
61 import java.util.ArrayList;
62 import java.util.HashMap;
63 import java.util.List;
64 import java.util.Map;
65
66 public class SeqAnnotationServiceCalcWorker extends AlignCalcWorker
67         implements WSAnnotationCalcManagerI
68 {
69
70   protected ServiceWithParameters service;
71
72   protected WsParamSetI preset;
73
74   protected List<ArgumentI> arguments;
75
76   protected IProgressIndicator guiProgress;
77
78   protected boolean submitGaps = true;
79
80   /**
81    * by default, we filter out non-standard residues before submission
82    */
83   protected boolean filterNonStandardResidues = true;
84
85   /**
86    * Recover any existing parameters for this service
87    */
88   protected void initViewportParams()
89   {
90     if (getCalcId() != null)
91     {
92       ((jalview.gui.AlignViewport) alignViewport).setCalcIdSettingsFor(
93               getCalcId(),
94               new AAConSettings(true, service, this.preset, arguments),
95               true);
96     }
97   }
98
99   /**
100    * 
101    * @return null or a string used to recover all annotation generated by this
102    *         worker
103    */
104   public String getCalcId()
105   {
106     return service.getAlignAnalysisUI() == null ? null
107             : service.getAlignAnalysisUI().getCalcId();
108   }
109
110   public WsParamSetI getPreset()
111   {
112     return preset;
113   }
114
115   public List<ArgumentI> getArguments()
116   {
117     return arguments;
118   }
119
120   /**
121    * reconfigure and restart the AAConClient. This method will spawn a new
122    * thread that will wait until any current jobs are finished, modify the
123    * parameters and restart the conservation calculation with the new values.
124    * 
125    * @param newpreset
126    * @param newarguments
127    */
128   public void updateParameters(final WsParamSetI newpreset,
129           final List<ArgumentI> newarguments)
130   {
131     preset = newpreset;
132     arguments = newarguments;
133     calcMan.startWorker(this);
134     initViewportParams();
135   }
136   protected boolean alignedSeqs = true;
137
138   protected boolean nucleotidesAllowed = false;
139
140   protected boolean proteinAllowed = false;
141
142   /**
143    * record sequences for mapping result back to afterwards
144    */
145   protected boolean bySequence = false;
146
147   protected Map<String, SequenceI> seqNames;
148
149   // TODO: convert to bitset
150   protected boolean[] gapMap;
151
152   int realw;
153
154   protected int start;
155
156   int end;
157
158   private AlignFrame alignFrame;
159
160   public boolean[] getGapMap()
161   {
162     return gapMap;
163   }
164
165   public SeqAnnotationServiceCalcWorker(AlignViewportI alignViewport,
166           AlignmentViewPanel alignPanel)
167   {
168     super(alignViewport, alignPanel);
169   }
170
171   public SeqAnnotationServiceCalcWorker(ServiceWithParameters service,
172           AlignFrame alignFrame,
173           WsParamSetI preset, List<ArgumentI> paramset)
174   {
175     this(alignFrame.getCurrentView(), alignFrame.alignPanel);
176     // TODO: both these fields needed ?
177     this.alignFrame = alignFrame;
178     this.guiProgress = alignFrame;
179     this.preset = preset;
180     this.arguments = paramset;
181     this.service = service;
182     try
183     {
184       annotService = (jalview.ws.api.SequenceAnnotationServiceI) ((JalviewServiceEndpointProviderI) service)
185               .getEndpoint();
186     } catch (ClassCastException cce)
187     {
188       JvOptionPane.showMessageDialog(Desktop.desktop,
189               MessageManager.formatMessage(
190                       "label.service_called_is_not_an_annotation_service",
191                       new String[]
192                       { service.getName() }),
193               MessageManager.getString("label.internal_jalview_error"),
194               JvOptionPane.WARNING_MESSAGE);
195
196     }
197     // configure submission flags
198     proteinAllowed = service.isProteinService();
199     nucleotidesAllowed = service.isNucleotideService();
200     alignedSeqs = service.isNeedsAlignedSequences();
201     bySequence = !service.isAlignmentAnalysis();
202     filterNonStandardResidues = service.isFilterSymbols();
203     min_valid_seqs = service.getMinimumInputSequences();
204     submitGaps = service.isAlignmentAnalysis();
205
206     if (service.isInteractiveUpdate())
207     {
208       initViewportParams();
209     }
210   }
211
212   /**
213    * 
214    * @return true if the submission thread should attempt to submit data
215    */
216   public boolean hasService()
217   {
218     return annotService != null;
219   }
220
221   protected jalview.ws.api.SequenceAnnotationServiceI annotService = null;
222
223   volatile JobId rslt = null;
224
225   AnnotationWsJob running = null;
226
227   private int min_valid_seqs;
228
229   @Override
230   public void run() throws Exception
231   {
232     if (alignViewport.isClosed())
233     {
234       abortAndDestroy();
235       return;
236     }
237     if (!hasService())
238     {
239       return;
240     }
241
242     long progressId = -1;
243
244     int serverErrorsLeft = 3;
245     final boolean cancellable = CancellableI.class
246             .isAssignableFrom(annotService.getClass());
247     StringBuffer msg = new StringBuffer();
248     JobStateSummary job = new JobStateSummary();
249     WebserviceInfo info = new WebserviceInfo("foo", "bar", false);
250     try
251     {
252       List<SequenceI> seqs = getInputSequences(
253               alignViewport.getAlignment(),
254               bySequence ? alignViewport.getSelectionGroup() : null);
255
256       if (seqs == null || !checkValidInputSeqs(seqs))
257       {
258         jalview.bin.Cache.log.debug(
259                 "Sequences for analysis service were null or not valid");
260         return;
261       }
262
263       if (guiProgress != null)
264       {
265         guiProgress.setProgressBar(service.getActionText(),
266                 progressId = System.currentTimeMillis());
267       }
268       jalview.bin.Cache.log.debug("submitted " + seqs.size()
269               + " sequences to " + service.getActionText());
270
271       rslt = annotService.submitToService(seqs, getPreset(),
272               getArguments());
273       if (rslt == null)
274       {
275         return;
276       }
277       // TODO: handle job submission error reporting here.
278       Cache.log.debug("Service " + service.getUri() + "\nSubmitted job ID: "
279               + rslt);
280       ;
281       // ///
282       // otherwise, construct WsJob and any UI handlers
283       running = new AnnotationWsJob();
284       running.setJobHandle(rslt);
285       running.setSeqNames(seqNames);
286       running.setStartPos(start);
287       running.setSeqs(seqs);
288       job.updateJobPanelState(info, "", running);
289       if (guiProgress != null)
290       {
291         guiProgress.registerHandler(progressId,
292                 new IProgressIndicatorHandler()
293                 {
294
295                   @Override
296                   public boolean cancelActivity(long id)
297                   {
298                     ((CancellableI) annotService).cancel(running);
299                     return true;
300                   }
301
302                   @Override
303                   public boolean canCancel()
304                   {
305                     return cancellable;
306                   }
307                 });
308       }
309       
310       // ///
311       // and poll for updates until job finishes, fails or becomes stale
312       
313       boolean finished = false;
314       do
315       {
316         Cache.log.debug("Updating status for annotation service.");
317         annotService.updateStatus(running);
318         job.updateJobPanelState(info, "", running);
319         if (running.isSubjobComplete())
320         {
321           Cache.log.debug(
322                   "Finished polling analysis service job: status reported is "
323                           + running.getState());
324           finished = true;
325         }
326         else
327         {
328           Cache.log.debug("Status now " + running.getState());
329         }
330
331         // pull any stats - some services need to flush log output before
332         // results are available
333         Cache.log.debug("Updating progress log for annotation service.");
334
335         try
336         {
337           annotService.updateJobProgress(running);
338         } catch (Throwable thr)
339         {
340           Cache.log.debug("Ignoring exception during progress update.",
341                   thr);
342         }
343         Cache.log.trace("Result of poll: " + running.getStatus());
344
345         if (!finished && !running.isFailed())
346         {
347           try
348           {
349             Cache.log.debug("Analysis service job thread sleeping.");
350             Thread.sleep(200);
351             Cache.log.debug("Analysis service job thread woke.");
352           } catch (InterruptedException x)
353           {
354           }
355           ;
356         }
357       } while (!finished);
358
359       Cache.log.debug("Job poll loop exited. Job is " + running.getState());
360       // TODO: need to poll/retry
361       if (serverErrorsLeft > 0)
362       {
363         try
364         {
365           Thread.sleep(200);
366         } catch (InterruptedException x)
367         {
368         }
369       }
370       if (running.isFinished())
371       {
372         // expect there to be results to collect
373         // configure job with the associated view's feature renderer, if one
374         // exists.
375         // TODO: here one would also grab the 'master feature renderer' in order
376         // to enable/disable
377         // features automatically according to user preferences
378         running.setFeatureRenderer(
379                 ((jalview.gui.AlignmentPanel) ap).cloneFeatureRenderer());
380         Cache.log.debug("retrieving job results.");
381         final Map<String, FeatureColourI> featureColours = new HashMap<>();
382         final Map<String, FeatureMatcherSetI> featureFilters = new HashMap<>();
383         List<AlignmentAnnotation> returnedAnnot = annotService
384                 .getAnnotationResult(running.getJobHandle(), seqs,
385                         featureColours, featureFilters);
386
387         Cache.log.debug("Obtained " + (returnedAnnot == null ? "no rows"
388                 : ("" + returnedAnnot.size())));
389         Cache.log.debug("There were " + featureColours.size()
390                 + " feature colours and " + featureFilters.size()
391                 + " filters defined.");
392
393         // TODO
394         // copy over each annotation row reurned and also defined on each
395         // sequence, excluding regions not annotated due to gapMap/column
396         // visibility
397
398         // update calcId if it is not already set on returned annotation
399         if (returnedAnnot != null)
400         {
401           for (AlignmentAnnotation aa : returnedAnnot)
402           {
403             // assume that any CalcIds already set
404             if (getCalcId() != null && aa.getCalcId() == null
405                     || "".equals(aa.getCalcId()))
406             {
407               aa.setCalcId(getCalcId());
408             }
409             // autocalculated annotation are created by interactive alignment
410             // analysis services
411             aa.autoCalculated = service.isAlignmentAnalysis()
412                     && service.isInteractiveUpdate();
413           }
414         }
415
416         running.setAnnotation(returnedAnnot);
417
418         if (running.hasResults())
419         {
420           jalview.bin.Cache.log.debug("Updating result annotation from Job "
421                   + rslt + " at " + service.getUri());
422           updateResultAnnotation(true);
423           if (running.isTransferSequenceFeatures())
424           {
425             // TODO
426             // look at each sequence and lift over any features, excluding
427             // regions
428             // not annotated due to gapMap/column visibility
429
430             jalview.bin.Cache.log.debug(
431                     "Updating feature display settings and transferring features from Job "
432                             + rslt + " at " + service.getUri());
433             // TODO: consider merge rather than apply here
434             alignViewport.applyFeaturesStyle(new FeatureSettingsAdapter()
435             {
436               @Override
437               public FeatureColourI getFeatureColour(String type)
438               {
439                 return featureColours.get(type);
440               }
441
442               @Override
443               public FeatureMatcherSetI getFeatureFilters(String type)
444               {
445                 return featureFilters.get(type);
446               }
447
448               @Override
449               public boolean isFeatureDisplayed(String type)
450               {
451                 return featureColours.containsKey(type);
452               }
453
454             });
455             // TODO: JAL-1150 - create sequence feature settings API for
456             // defining
457             // styles and enabling/disabling feature overlay on alignment panel
458
459             if (alignFrame.alignPanel == ap)
460             {
461               alignViewport.setShowSequenceFeatures(true);
462               alignFrame.setMenusForViewport();
463             }
464           }
465           ap.adjustAnnotationHeight();
466         }
467       }
468       Cache.log.debug("Annotation Service Worker thread finished.");
469     }
470 // TODO: use service specitic exception handlers
471 //    catch (JobSubmissionException x)
472 //    {
473 //
474 //      System.err.println(
475 //              "submission error with " + getServiceActionText() + " :");
476 //      x.printStackTrace();
477 //      calcMan.disableWorker(this);
478 //    } catch (ResultNotAvailableException x)
479 //    {
480 //      System.err.println("collection error:\nJob ID: " + rslt);
481 //      x.printStackTrace();
482 //      calcMan.disableWorker(this);
483 //
484 //    } catch (OutOfMemoryError error)
485 //    {
486 //      calcMan.disableWorker(this);
487 //
488 //      ap.raiseOOMWarning(getServiceActionText(), error);
489 //    } 
490     catch (Throwable x)
491     {
492       calcMan.disableWorker(this);
493
494       System.err
495               .println("Blacklisting worker due to unexpected exception:");
496       x.printStackTrace();
497       throw new Exception(x);
498     } finally
499     {
500
501       if (ap != null)
502       {
503         if (guiProgress != null && progressId != -1)
504         {
505           guiProgress.setProgressBar("", progressId);
506         }
507         // TODO: may not need to paintAlignment again !
508         ap.paintAlignment(false, false);
509       }
510       if (msg.length() > 0)
511       {
512         // TODO: stash message somewhere in annotation or alignment view.
513         // code below shows result in a text box popup
514         /*
515          * jalview.gui.CutAndPasteTransfer cap = new
516          * jalview.gui.CutAndPasteTransfer(); cap.setText(msg.toString());
517          * jalview.gui.Desktop.addInternalFrame(cap,
518          * "Job Status for "+getServiceActionText(), 600, 400);
519          */
520       }
521     }
522
523   }
524
525   /**
526    * validate input for dynamic/non-dynamic update context TODO: move to
527    * analysis interface ?
528    * @param seqs
529    * 
530    * @return true if input is valid
531    */
532   boolean checkValidInputSeqs(List<SequenceI> seqs)
533   {
534     int nvalid = 0;
535     for (SequenceI sq : seqs)
536     {
537       if (sq.getStart() <= sq.getEnd()
538               && (sq.isProtein() ? proteinAllowed : nucleotidesAllowed))
539       {
540         if (submitGaps
541                 || sq.getLength() == (sq.getEnd() - sq.getStart() + 1))
542         {
543           nvalid++;
544         }
545       }
546     }
547     return nvalid >= min_valid_seqs;
548   }
549
550   public void cancelCurrentJob()
551   {
552     try
553     {
554       String id = running.getJobId();
555       if (((CancellableI) annotService).cancel(running))
556       {
557         System.err.println("Cancelled job " + id);
558       }
559       else
560       {
561         System.err.println("Job " + id + " couldn't be cancelled.");
562       }
563     } catch (Exception q)
564     {
565       q.printStackTrace();
566     }
567   }
568
569   /**
570    * Interactive updating. Analysis calculations that work on the currently
571    * displayed alignment data should cancel existing jobs when the input data
572    * has changed.
573    * 
574    * @return true if a running job should be cancelled because new input data is
575    *         available for analysis
576    */
577   boolean isInteractiveUpdate()
578   {
579     return service.isInteractiveUpdate();
580   }
581
582   /**
583    * decide what sequences will be analysed TODO: refactor to generate
584    * List<SequenceI> for submission to service interface
585    * 
586    * @param alignment
587    * @param inputSeqs
588    * @return
589    */
590   public List<SequenceI> getInputSequences(AlignmentI alignment,
591           AnnotatedCollectionI inputSeqs)
592   {
593     if (alignment == null || alignment.getWidth() <= 0
594             || alignment.getSequences() == null || alignment.isNucleotide()
595                     ? !nucleotidesAllowed
596                     : !proteinAllowed)
597     {
598       return null;
599     }
600     if (inputSeqs == null || inputSeqs.getWidth() <= 0
601             || inputSeqs.getSequences() == null
602             || inputSeqs.getSequences().size() < 1)
603     {
604       inputSeqs = alignment;
605     }
606
607     List<SequenceI> seqs = new ArrayList<>();
608
609     int minlen = 10;
610     int ln = -1;
611     if (bySequence)
612     {
613       seqNames = new HashMap<>();
614     }
615     gapMap = new boolean[0];
616     start = inputSeqs.getStartRes();
617     end = inputSeqs.getEndRes();
618     // TODO: URGENT! unify with JPred / MSA code to handle hidden regions
619     // correctly
620     // TODO: push attributes into WsJob instance (so they can be safely
621     // persisted/restored
622     for (SequenceI sq : (inputSeqs.getSequences()))
623     {
624       if (bySequence
625               ? sq.findPosition(end + 1)
626                       - sq.findPosition(start + 1) > minlen - 1
627               : sq.getEnd() - sq.getStart() > minlen - 1)
628       {
629         String newname = SeqsetUtils.unique_name(seqs.size() + 1);
630         // make new input sequence with or without gaps
631         if (seqNames != null)
632         {
633           seqNames.put(newname, sq);
634         }
635         SequenceI seq;
636         if (submitGaps)
637         {
638           seqs.add(seq = new jalview.datamodel.Sequence(newname,
639                   sq.getSequenceAsString()));
640           if (gapMap == null || gapMap.length < seq.getLength())
641           {
642             boolean[] tg = gapMap;
643             gapMap = new boolean[seq.getLength()];
644             System.arraycopy(tg, 0, gapMap, 0, tg.length);
645             for (int p = tg.length; p < gapMap.length; p++)
646             {
647               gapMap[p] = false; // init as a gap
648             }
649           }
650           for (int apos : sq.gapMap())
651           {
652             char sqc = sq.getCharAt(apos);
653             if (!filterNonStandardResidues
654                     || (sq.isProtein() ? ResidueProperties.aaIndex[sqc] < 20
655                             : ResidueProperties.nucleotideIndex[sqc] < 5))
656             {
657               gapMap[apos] = true; // aligned and real amino acid residue
658             }
659             ;
660           }
661         }
662         else
663         {
664           // TODO: add ability to exclude hidden regions
665           seqs.add(seq = new jalview.datamodel.Sequence(newname,
666                   AlignSeq.extractGaps(jalview.util.Comparison.GapChars,
667                           sq.getSequenceAsString(start, end + 1))));
668           // for annotation need to also record map to sequence start/end
669           // position in range
670           // then transfer back to original sequence on return.
671         }
672         if (seq.getLength() > ln)
673         {
674           ln = seq.getLength();
675         }
676       }
677     }
678     if (alignedSeqs && submitGaps)
679     {
680       realw = 0;
681       for (int i = 0; i < gapMap.length; i++)
682       {
683         if (gapMap[i])
684         {
685           realw++;
686         }
687       }
688       // try real hard to return something submittable
689       // TODO: some of AAcon measures need a minimum of two or three amino
690       // acids at each position, and AAcon doesn't gracefully degrade.
691       for (int p = 0; p < seqs.size(); p++)
692       {
693         SequenceI sq = seqs.get(p);
694         // strip gapped columns
695         char[] padded = new char[realw],
696                 orig = sq.getSequence();
697         for (int i = 0, pp = 0; i < realw; pp++)
698         {
699           if (gapMap[pp])
700           {
701             if (orig.length > pp)
702             {
703               padded[i++] = orig[pp];
704             }
705             else
706             {
707               padded[i++] = '-';
708             }
709           }
710         }
711         seqs.set(p, new jalview.datamodel.Sequence(sq.getName(),
712                 new String(padded)));
713       }
714     }
715     return seqs;
716   }
717
718   @Override
719   public void updateAnnotation()
720   {
721     updateResultAnnotation(false);
722   }
723
724   public void updateResultAnnotation(boolean immediate)
725   {
726     if ((immediate || !calcMan.isWorking(this)) && running != null
727             && running.hasResults())
728     {
729       List<AlignmentAnnotation> ourAnnot = running.getAnnotation(),
730               newAnnots = new ArrayList<>();
731       //
732       // update graphGroup for all annotation
733       //
734       /**
735        * find a graphGroup greater than any existing ones this could be a method
736        * provided by alignment Alignment.getNewGraphGroup() - returns next
737        * unused graph group
738        */
739       int graphGroup = 1;
740       if (alignViewport.getAlignment().getAlignmentAnnotation() != null)
741       {
742         for (AlignmentAnnotation ala : alignViewport.getAlignment()
743                 .getAlignmentAnnotation())
744         {
745           if (ala.graphGroup > graphGroup)
746           {
747             graphGroup = ala.graphGroup;
748           }
749         }
750       }
751       /**
752        * update graphGroup in the annotation rows returned from service
753        */
754       // TODO: look at sequence annotation rows and update graph groups in the
755       // case of reference annotation.
756       for (AlignmentAnnotation ala : ourAnnot)
757       {
758         if (ala.graphGroup > 0)
759         {
760           ala.graphGroup += graphGroup;
761         }
762         SequenceI aseq = null;
763
764         /**
765          * transfer sequence refs and adjust gapmap
766          */
767         if (ala.sequenceRef != null)
768         {
769           SequenceI seq = running.getSeqNames()
770                   .get(ala.sequenceRef.getName());
771           aseq = seq;
772           while (seq.getDatasetSequence() != null)
773           {
774             seq = seq.getDatasetSequence();
775           }
776         }
777         Annotation[] resAnnot = ala.annotations,
778                 gappedAnnot = new Annotation[Math.max(
779                         alignViewport.getAlignment().getWidth(),
780                         gapMap.length)];
781         for (int p = 0, ap = start; ap < gappedAnnot.length; ap++)
782         {
783           if (gapMap != null && gapMap.length > ap && !gapMap[ap])
784           {
785             gappedAnnot[ap] = new Annotation("", "", ' ', Float.NaN);
786           }
787           else if (p < resAnnot.length)
788           {
789             gappedAnnot[ap] = resAnnot[p++];
790           }
791         }
792         ala.sequenceRef = aseq;
793         ala.annotations = gappedAnnot;
794         AlignmentAnnotation newAnnot = getAlignViewport().getAlignment()
795                 .updateFromOrCopyAnnotation(ala);
796         if (aseq != null)
797         {
798
799           aseq.addAlignmentAnnotation(newAnnot);
800           newAnnot.adjustForAlignment();
801
802           AlignmentAnnotationUtils.replaceAnnotationOnAlignmentWith(
803                   newAnnot, newAnnot.label, newAnnot.getCalcId());
804         }
805         newAnnots.add(newAnnot);
806
807       }
808       for (SequenceI sq : running.getSeqs())
809       {
810         if (!sq.getFeatures().hasFeatures()
811                 && (sq.getDBRefs() == null || sq.getDBRefs().size() == 0))
812         {
813           continue;
814         }
815         running.setTransferSequenceFeatures(true);
816         SequenceI seq = running.getSeqNames().get(sq.getName());
817         SequenceI dseq;
818         ContiguousI seqRange = seq.findPositions(start, end);
819
820         while ((dseq = seq).getDatasetSequence() != null)
821         {
822           seq = seq.getDatasetSequence();
823         }
824         List<ContiguousI> sourceRange = new ArrayList();
825         if (gapMap != null && gapMap.length >= end)
826         {
827           int lastcol = start, col = start;
828           do
829           {
830             if (col == end || !gapMap[col])
831             {
832               if (lastcol <= (col - 1))
833               {
834                 seqRange = seq.findPositions(lastcol, col);
835                 sourceRange.add(seqRange);
836               }
837               lastcol = col + 1;
838             }
839           } while (++col <= end);
840         }
841         else
842         {
843           sourceRange.add(seq.findPositions(start, end));
844         }
845         int i = 0;
846         int source_startend[] = new int[sourceRange.size() * 2];
847
848         for (ContiguousI range : sourceRange)
849         {
850           source_startend[i++] = range.getBegin();
851           source_startend[i++] = range.getEnd();
852         }
853         Mapping mp = new Mapping(
854                 new MapList(source_startend, new int[]
855                 { seq.getStart(), seq.getEnd() }, 1, 1));
856         dseq.transferAnnotation(sq, mp);
857
858       }
859       updateOurAnnots(newAnnots);
860     }
861   }
862
863   protected void updateOurAnnots(List<AlignmentAnnotation> ourAnnot)
864   {
865     List<AlignmentAnnotation> our = ourAnnots;
866     ourAnnots = ourAnnot;
867     AlignmentI alignment = alignViewport.getAlignment();
868     if (our != null)
869     {
870       if (our.size() > 0)
871       {
872         for (AlignmentAnnotation an : our)
873         {
874           if (!ourAnnots.contains(an))
875           {
876             // remove the old annotation
877             alignment.deleteAnnotation(an);
878           }
879         }
880       }
881       our.clear();
882     }
883
884     // validate rows and update Alignmment state
885     for (AlignmentAnnotation an : ourAnnots)
886     {
887       alignViewport.getAlignment().validateAnnotation(an);
888     }
889     // TODO: may need a menu refresh after this
890     // af.setMenusForViewport();
891     ap.adjustAnnotationHeight();
892
893   }
894
895   public SequenceAnnotationServiceI getService()
896   {
897     return annotService;
898   }
899
900 }