fcb6e281d53e073fac8b1f80f0cd60d0888408eb
[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()
231   {
232     if (checkDone())
233     {
234       return;
235     }
236     if (!hasService())
237     {
238       calcMan.workerComplete(this);
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         calcMan.workerComplete(this);
261         return;
262       }
263
264       if (guiProgress != null)
265       {
266         guiProgress.setProgressBar(service.getActionText(),
267                 progressId = System.currentTimeMillis());
268       }
269       jalview.bin.Cache.log.debug("submitted " + seqs.size()
270               + " sequences to " + service.getActionText());
271
272       rslt = annotService.submitToService(seqs, getPreset(),
273               getArguments());
274       if (rslt == null)
275       {
276         return;
277       }
278       // TODO: handle job submission error reporting here.
279       
280       // ///
281       // otherwise, construct WsJob and any UI handlers
282       running = new AnnotationWsJob();
283       running.setJobHandle(rslt);
284       running.setSeqNames(seqNames);
285       running.setStartPos(start);
286       running.setSeqs(seqs);
287       job.updateJobPanelState(info, "", running);
288       if (guiProgress != null)
289       {
290         guiProgress.registerHandler(progressId,
291                 new IProgressIndicatorHandler()
292                 {
293
294                   @Override
295                   public boolean cancelActivity(long id)
296                   {
297                     ((CancellableI) annotService).cancel(running);
298                     return true;
299                   }
300
301                   @Override
302                   public boolean canCancel()
303                   {
304                     return cancellable;
305                   }
306                 });
307       }
308       
309       // ///
310       // and poll for updates until job finishes, fails or becomes stale
311       
312       boolean finished = false;
313       do
314       {
315         Cache.log.debug("Updating status for annotation service.");
316         annotService.updateStatus(running);
317         job.updateJobPanelState(info, "", running);
318         if (running.isSubjobComplete())
319         {
320           Cache.log.debug(
321                   "Finished polling analysis service job: status reported is "
322                           + running.getState());
323           finished = true;
324         }
325         else
326         {
327           Cache.log.debug("Status now " + running.getState());
328         }
329
330         if (calcMan.isPending(this) && isInteractiveUpdate())
331         {
332           Cache.log.debug("Analysis service job is stale. aborting.");
333           // job has become stale.
334           if (!finished) {
335             finished = true;
336             // cancel this job and yield to the new job
337             try
338             {
339               if (cancellable
340                         && ((CancellableI) annotService).cancel(running))
341               {
342                 System.err.println("Cancelled job: " + rslt);
343               }
344               else
345               {
346                 System.err.println("FAILED TO CANCEL job: " + rslt);
347               }
348   
349             } catch (Exception x)
350             {
351   
352             }
353           }
354           rslt = running.getJobHandle();
355           return;
356         }
357
358         // pull any stats - some services need to flush log output before
359         // results are available
360         Cache.log.debug("Updating progress log for annotation service.");
361
362         try
363         {
364         annotService.updateJobProgress(running);
365         } catch (Throwable thr)
366         {
367           Cache.log.debug("Ignoring exception during progress update.",
368                   thr);
369         }
370         Cache.log.trace("Result of poll: " + running.getStatus());
371
372         if (!finished && !running.isFailed())
373         {
374           try
375           {
376             Cache.log.debug("Analysis service job thread sleeping.");
377             Thread.sleep(200);
378             Cache.log.debug("Analysis service job thread woke.");
379           } catch (InterruptedException x)
380           {
381           }
382           ;
383         }
384       } while (!finished);
385
386       Cache.log.debug("Job poll loop exited. Job is " + running.getState());
387       // TODO: need to poll/retry
388       if (serverErrorsLeft > 0)
389       {
390         try
391         {
392           Thread.sleep(200);
393         } catch (InterruptedException x)
394         {
395         }
396       }
397       if (running.isFinished())
398       {
399         // expect there to be results to collect
400         // configure job with the associated view's feature renderer, if one
401         // exists.
402         // TODO: here one would also grab the 'master feature renderer' in order
403         // to enable/disable
404         // features automatically according to user preferences
405         running.setFeatureRenderer(
406                 ((jalview.gui.AlignmentPanel) ap).cloneFeatureRenderer());
407         Cache.log.debug("retrieving job results.");
408         final Map<String, FeatureColourI> featureColours = new HashMap<>();
409         final Map<String, FeatureMatcherSetI> featureFilters = new HashMap<>();
410         List<AlignmentAnnotation> returnedAnnot = annotService
411                 .getAnnotationResult(running.getJobHandle(), seqs,
412                         featureColours, featureFilters);
413
414         Cache.log.debug("Obtained " + (returnedAnnot == null ? "no rows"
415                 : ("" + returnedAnnot.size())));
416         Cache.log.debug("There were " + featureColours.size()
417                 + " feature colours and " + featureFilters.size()
418                 + " filters defined.");
419
420         // TODO
421         // copy over each annotation row reurned and also defined on each
422         // sequence, excluding regions not annotated due to gapMap/column
423         // visibility
424
425         running.setAnnotation(returnedAnnot);
426
427         if (running.hasResults())
428         {
429           jalview.bin.Cache.log.debug("Updating result annotation from Job "
430                   + rslt + " at " + service.getUri());
431           updateResultAnnotation(true);
432           if (running.isTransferSequenceFeatures())
433           {
434             // TODO
435             // look at each sequence and lift over any features, excluding
436             // regions
437             // not annotated due to gapMap/column visibility
438
439             jalview.bin.Cache.log.debug(
440                     "Updating feature display settings and transferring features from Job "
441                             + rslt + " at " + service.getUri());
442             // TODO: consider merge rather than apply here
443             alignViewport.applyFeaturesStyle(new FeatureSettingsAdapter()
444             {
445               @Override
446               public FeatureColourI getFeatureColour(String type)
447               {
448                 return featureColours.get(type);
449               }
450
451               @Override
452               public FeatureMatcherSetI getFeatureFilters(String type)
453               {
454                 return featureFilters.get(type);
455               }
456
457               @Override
458               public boolean isFeatureDisplayed(String type)
459               {
460                 return featureColours.containsKey(type);
461               }
462
463             });
464             // TODO: JAL-1150 - create sequence feature settings API for
465             // defining
466             // styles and enabling/disabling feature overlay on alignment panel
467
468             if (alignFrame.alignPanel == ap)
469             {
470               alignViewport.setShowSequenceFeatures(true);
471               alignFrame.setMenusForViewport();
472             }
473           }
474           ap.adjustAnnotationHeight();
475         }
476       }
477       Cache.log.debug("Annotation Service Worker thread finished.");
478     }
479 // TODO: use service specitic exception handlers
480 //    catch (JobSubmissionException x)
481 //    {
482 //
483 //      System.err.println(
484 //              "submission error with " + getServiceActionText() + " :");
485 //      x.printStackTrace();
486 //      calcMan.disableWorker(this);
487 //    } catch (ResultNotAvailableException x)
488 //    {
489 //      System.err.println("collection error:\nJob ID: " + rslt);
490 //      x.printStackTrace();
491 //      calcMan.disableWorker(this);
492 //
493 //    } catch (OutOfMemoryError error)
494 //    {
495 //      calcMan.disableWorker(this);
496 //
497 //      ap.raiseOOMWarning(getServiceActionText(), error);
498 //    } 
499     catch (Throwable x)
500     {
501       calcMan.disableWorker(this);
502
503       System.err
504               .println("Blacklisting worker due to unexpected exception:");
505       x.printStackTrace();
506     } finally
507     {
508
509       calcMan.workerComplete(this);
510       if (ap != null)
511       {
512         if (guiProgress != null && progressId != -1)
513         {
514           guiProgress.setProgressBar("", progressId);
515         }
516         // TODO: may not need to paintAlignment again !
517         ap.paintAlignment(false, false);
518       }
519       if (msg.length() > 0)
520       {
521         // TODO: stash message somewhere in annotation or alignment view.
522         // code below shows result in a text box popup
523         /*
524          * jalview.gui.CutAndPasteTransfer cap = new
525          * jalview.gui.CutAndPasteTransfer(); cap.setText(msg.toString());
526          * jalview.gui.Desktop.addInternalFrame(cap,
527          * "Job Status for "+getServiceActionText(), 600, 400);
528          */
529       }
530     }
531
532   }
533
534   /**
535    * validate input for dynamic/non-dynamic update context TODO: move to
536    * analysis interface ?
537    * @param seqs
538    * 
539    * @return true if input is valid
540    */
541   boolean checkValidInputSeqs(List<SequenceI> seqs)
542   {
543     int nvalid = 0;
544     for (SequenceI sq : seqs)
545     {
546       if (sq.getStart() <= sq.getEnd()
547               && (sq.isProtein() ? proteinAllowed : nucleotidesAllowed))
548       {
549         if (submitGaps
550                 || sq.getLength() == (sq.getEnd() - sq.getStart() + 1))
551         {
552           nvalid++;
553         }
554       }
555     }
556     return nvalid >= min_valid_seqs;
557   }
558
559   public void cancelCurrentJob()
560   {
561     try
562     {
563       String id = running.getJobId();
564       if (((CancellableI) annotService).cancel(running))
565       {
566         System.err.println("Cancelled job " + id);
567       }
568       else
569       {
570         System.err.println("Job " + id + " couldn't be cancelled.");
571       }
572     } catch (Exception q)
573     {
574       q.printStackTrace();
575     }
576   }
577
578   /**
579    * Interactive updating. Analysis calculations that work on the currently
580    * displayed alignment data should cancel existing jobs when the input data
581    * has changed.
582    * 
583    * @return true if a running job should be cancelled because new input data is
584    *         available for analysis
585    */
586   boolean isInteractiveUpdate()
587   {
588     return service.isInteractiveUpdate();
589   }
590
591   /**
592    * decide what sequences will be analysed TODO: refactor to generate
593    * List<SequenceI> for submission to service interface
594    * 
595    * @param alignment
596    * @param inputSeqs
597    * @return
598    */
599   public List<SequenceI> getInputSequences(AlignmentI alignment,
600           AnnotatedCollectionI inputSeqs)
601   {
602     if (alignment == null || alignment.getWidth() <= 0
603             || alignment.getSequences() == null || alignment.isNucleotide()
604                     ? !nucleotidesAllowed
605                     : !proteinAllowed)
606     {
607       return null;
608     }
609     if (inputSeqs == null || inputSeqs.getWidth() <= 0
610             || inputSeqs.getSequences() == null
611             || inputSeqs.getSequences().size() < 1)
612     {
613       inputSeqs = alignment;
614     }
615
616     List<SequenceI> seqs = new ArrayList<>();
617
618     int minlen = 10;
619     int ln = -1;
620     if (bySequence)
621     {
622       seqNames = new HashMap<>();
623     }
624     gapMap = new boolean[0];
625     start = inputSeqs.getStartRes();
626     end = inputSeqs.getEndRes();
627     // TODO: URGENT! unify with JPred / MSA code to handle hidden regions
628     // correctly
629     // TODO: push attributes into WsJob instance (so they can be safely
630     // persisted/restored
631     for (SequenceI sq : (inputSeqs.getSequences()))
632     {
633       if (bySequence
634               ? sq.findPosition(end + 1)
635                       - sq.findPosition(start + 1) > minlen - 1
636               : sq.getEnd() - sq.getStart() > minlen - 1)
637       {
638         String newname = SeqsetUtils.unique_name(seqs.size() + 1);
639         // make new input sequence with or without gaps
640         if (seqNames != null)
641         {
642           seqNames.put(newname, sq);
643         }
644         SequenceI seq;
645         if (submitGaps)
646         {
647           seqs.add(seq = new jalview.datamodel.Sequence(newname,
648                   sq.getSequenceAsString()));
649           if (gapMap == null || gapMap.length < seq.getLength())
650           {
651             boolean[] tg = gapMap;
652             gapMap = new boolean[seq.getLength()];
653             System.arraycopy(tg, 0, gapMap, 0, tg.length);
654             for (int p = tg.length; p < gapMap.length; p++)
655             {
656               gapMap[p] = false; // init as a gap
657             }
658           }
659           for (int apos : sq.gapMap())
660           {
661             char sqc = sq.getCharAt(apos);
662             if (!filterNonStandardResidues
663                     || (sq.isProtein() ? ResidueProperties.aaIndex[sqc] < 20
664                             : ResidueProperties.nucleotideIndex[sqc] < 5))
665             {
666               gapMap[apos] = true; // aligned and real amino acid residue
667             }
668             ;
669           }
670         }
671         else
672         {
673           // TODO: add ability to exclude hidden regions
674           seqs.add(seq = new jalview.datamodel.Sequence(newname,
675                   AlignSeq.extractGaps(jalview.util.Comparison.GapChars,
676                           sq.getSequenceAsString(start, end + 1))));
677           // for annotation need to also record map to sequence start/end
678           // position in range
679           // then transfer back to original sequence on return.
680         }
681         if (seq.getLength() > ln)
682         {
683           ln = seq.getLength();
684         }
685       }
686     }
687     if (alignedSeqs && submitGaps)
688     {
689       realw = 0;
690       for (int i = 0; i < gapMap.length; i++)
691       {
692         if (gapMap[i])
693         {
694           realw++;
695         }
696       }
697       // try real hard to return something submittable
698       // TODO: some of AAcon measures need a minimum of two or three amino
699       // acids at each position, and AAcon doesn't gracefully degrade.
700       for (int p = 0; p < seqs.size(); p++)
701       {
702         SequenceI sq = seqs.get(p);
703         // strip gapped columns
704         char[] padded = new char[realw],
705                 orig = sq.getSequence();
706         for (int i = 0, pp = 0; i < realw; pp++)
707         {
708           if (gapMap[pp])
709           {
710             if (orig.length > pp)
711             {
712               padded[i++] = orig[pp];
713             }
714             else
715             {
716               padded[i++] = '-';
717             }
718           }
719         }
720         seqs.set(p, new jalview.datamodel.Sequence(sq.getName(),
721                 new String(padded)));
722       }
723     }
724     return seqs;
725   }
726
727   @Override
728   public void updateAnnotation()
729   {
730     updateResultAnnotation(false);
731   }
732
733   public void updateResultAnnotation(boolean immediate)
734   {
735     if ((immediate || !calcMan.isWorking(this)) && running != null
736             && running.hasResults())
737     {
738       List<AlignmentAnnotation> ourAnnot = running.getAnnotation(),
739               newAnnots = new ArrayList<>();
740       //
741       // update graphGroup for all annotation
742       //
743       /**
744        * find a graphGroup greater than any existing ones this could be a method
745        * provided by alignment Alignment.getNewGraphGroup() - returns next
746        * unused graph group
747        */
748       int graphGroup = 1;
749       if (alignViewport.getAlignment().getAlignmentAnnotation() != null)
750       {
751         for (AlignmentAnnotation ala : alignViewport.getAlignment()
752                 .getAlignmentAnnotation())
753         {
754           if (ala.graphGroup > graphGroup)
755           {
756             graphGroup = ala.graphGroup;
757           }
758         }
759       }
760       /**
761        * update graphGroup in the annotation rows returned from service
762        */
763       // TODO: look at sequence annotation rows and update graph groups in the
764       // case of reference annotation.
765       for (AlignmentAnnotation ala : ourAnnot)
766       {
767         if (ala.graphGroup > 0)
768         {
769           ala.graphGroup += graphGroup;
770         }
771         SequenceI aseq = null;
772
773         /**
774          * transfer sequence refs and adjust gapmap
775          */
776         if (ala.sequenceRef != null)
777         {
778           SequenceI seq = running.getSeqNames()
779                   .get(ala.sequenceRef.getName());
780           aseq = seq;
781           while (seq.getDatasetSequence() != null)
782           {
783             seq = seq.getDatasetSequence();
784           }
785         }
786         Annotation[] resAnnot = ala.annotations,
787                 gappedAnnot = new Annotation[Math.max(
788                         alignViewport.getAlignment().getWidth(),
789                         gapMap.length)];
790         for (int p = 0, ap = start; ap < gappedAnnot.length; ap++)
791         {
792           if (gapMap != null && gapMap.length > ap && !gapMap[ap])
793           {
794             gappedAnnot[ap] = new Annotation("", "", ' ', Float.NaN);
795           }
796           else if (p < resAnnot.length)
797           {
798             gappedAnnot[ap] = resAnnot[p++];
799           }
800         }
801         ala.sequenceRef = aseq;
802         ala.annotations = gappedAnnot;
803         AlignmentAnnotation newAnnot = getAlignViewport().getAlignment()
804                 .updateFromOrCopyAnnotation(ala);
805         if (aseq != null)
806         {
807
808           aseq.addAlignmentAnnotation(newAnnot);
809           newAnnot.adjustForAlignment();
810
811           AlignmentAnnotationUtils.replaceAnnotationOnAlignmentWith(
812                   newAnnot, newAnnot.label, newAnnot.getCalcId());
813         }
814         newAnnots.add(newAnnot);
815
816       }
817       for (SequenceI sq : running.getSeqs())
818       {
819         if (!sq.getFeatures().hasFeatures()
820                 && (sq.getDBRefs() == null || sq.getDBRefs().length == 0))
821         {
822           continue;
823         }
824         running.setTransferSequenceFeatures(true);
825         SequenceI seq = running.getSeqNames().get(sq.getName());
826         SequenceI dseq;
827         ContiguousI seqRange = seq.findPositions(start, end);
828
829         while ((dseq = seq).getDatasetSequence() != null)
830         {
831           seq = seq.getDatasetSequence();
832         }
833         List<ContiguousI> sourceRange = new ArrayList();
834         if (gapMap != null && gapMap.length >= end)
835         {
836           int lastcol = start, col = start;
837           do
838           {
839             if (col == end || !gapMap[col])
840             {
841               if (lastcol <= (col - 1))
842               {
843                 seqRange = seq.findPositions(lastcol, col);
844                 sourceRange.add(seqRange);
845               }
846               lastcol = col + 1;
847             }
848           } while (++col <= end);
849         }
850         else
851         {
852           sourceRange.add(seq.findPositions(start, end));
853         }
854         int i = 0;
855         int source_startend[] = new int[sourceRange.size() * 2];
856
857         for (ContiguousI range : sourceRange)
858         {
859           source_startend[i++] = range.getBegin();
860           source_startend[i++] = range.getEnd();
861         }
862         Mapping mp = new Mapping(
863                 new MapList(source_startend, new int[]
864                 { seq.getStart(), seq.getEnd() }, 1, 1));
865         dseq.transferAnnotation(sq, mp);
866
867       }
868       updateOurAnnots(newAnnots);
869     }
870   }
871
872   /**
873    * notify manager that we have started, and wait for a free calculation slot
874    * 
875    * @return true if slot is obtained and work still valid, false if another
876    *         thread has done our work for us.
877    */
878   protected boolean checkDone()
879   {
880     calcMan.notifyStart(this);
881     ap.paintAlignment(false, false);
882     while (!calcMan.notifyWorking(this))
883     {
884       if (calcMan.isWorking(this))
885       {
886         return true;
887       }
888       try
889       {
890         if (ap != null)
891         {
892           ap.paintAlignment(false, false);
893         }
894
895         Thread.sleep(200);
896       } catch (Exception ex)
897       {
898         ex.printStackTrace();
899       }
900     }
901     if (alignViewport.isClosed())
902     {
903       abortAndDestroy();
904       return true;
905     }
906     return false;
907   }
908
909   protected void updateOurAnnots(List<AlignmentAnnotation> ourAnnot)
910   {
911     List<AlignmentAnnotation> our = ourAnnots;
912     ourAnnots = ourAnnot;
913     AlignmentI alignment = alignViewport.getAlignment();
914     if (our != null)
915     {
916       if (our.size() > 0)
917       {
918         for (AlignmentAnnotation an : our)
919         {
920           if (!ourAnnots.contains(an))
921           {
922             // remove the old annotation
923             alignment.deleteAnnotation(an);
924           }
925         }
926       }
927       our.clear();
928     }
929
930     // validate rows and update Alignmment state
931     for (AlignmentAnnotation an : ourAnnots)
932     {
933       alignViewport.getAlignment().validateAnnotation(an);
934     }
935     // TODO: may need a menu refresh after this
936     // af.setMenusForViewport();
937     ap.adjustAnnotationHeight();
938
939   }
940
941   public SequenceAnnotationServiceI getService()
942   {
943     return annotService;
944   }
945
946 }