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