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