132408b1aa4becc95017af2a2bb52b393e6428ce
[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         // update calcId if it is not already set on returned annotation
428         if (returnedAnnot != null)
429         {
430           for (AlignmentAnnotation aa : returnedAnnot)
431           {
432             // assume that any CalcIds already set
433             if (getCalcId() != null && aa.getCalcId() == null
434                     || "".equals(aa.getCalcId()))
435             {
436               aa.setCalcId(getCalcId());
437             }
438             // autocalculated annotation are created by interactive alignment
439             // analysis services
440             aa.autoCalculated = service.isAlignmentAnalysis()
441                     && service.isInteractiveUpdate();
442           }
443         }
444
445         running.setAnnotation(returnedAnnot);
446
447         if (running.hasResults())
448         {
449           jalview.bin.Cache.log.debug("Updating result annotation from Job "
450                   + rslt + " at " + service.getUri());
451           updateResultAnnotation(true);
452           if (running.isTransferSequenceFeatures())
453           {
454             // TODO
455             // look at each sequence and lift over any features, excluding
456             // regions
457             // not annotated due to gapMap/column visibility
458
459             jalview.bin.Cache.log.debug(
460                     "Updating feature display settings and transferring features from Job "
461                             + rslt + " at " + service.getUri());
462             // TODO: consider merge rather than apply here
463             alignViewport.applyFeaturesStyle(new FeatureSettingsAdapter()
464             {
465               @Override
466               public FeatureColourI getFeatureColour(String type)
467               {
468                 return featureColours.get(type);
469               }
470
471               @Override
472               public FeatureMatcherSetI getFeatureFilters(String type)
473               {
474                 return featureFilters.get(type);
475               }
476
477               @Override
478               public boolean isFeatureDisplayed(String type)
479               {
480                 return featureColours.containsKey(type);
481               }
482
483             });
484             // TODO: JAL-1150 - create sequence feature settings API for
485             // defining
486             // styles and enabling/disabling feature overlay on alignment panel
487
488             if (alignFrame.alignPanel == ap)
489             {
490               alignViewport.setShowSequenceFeatures(true);
491               alignFrame.setMenusForViewport();
492             }
493           }
494           ap.adjustAnnotationHeight();
495         }
496       }
497       Cache.log.debug("Annotation Service Worker thread finished.");
498     }
499 // TODO: use service specitic exception handlers
500 //    catch (JobSubmissionException x)
501 //    {
502 //
503 //      System.err.println(
504 //              "submission error with " + getServiceActionText() + " :");
505 //      x.printStackTrace();
506 //      calcMan.disableWorker(this);
507 //    } catch (ResultNotAvailableException x)
508 //    {
509 //      System.err.println("collection error:\nJob ID: " + rslt);
510 //      x.printStackTrace();
511 //      calcMan.disableWorker(this);
512 //
513 //    } catch (OutOfMemoryError error)
514 //    {
515 //      calcMan.disableWorker(this);
516 //
517 //      ap.raiseOOMWarning(getServiceActionText(), error);
518 //    } 
519     catch (Throwable x)
520     {
521       calcMan.disableWorker(this);
522
523       System.err
524               .println("Blacklisting worker due to unexpected exception:");
525       x.printStackTrace();
526     } finally
527     {
528
529       calcMan.workerComplete(this);
530       if (ap != null)
531       {
532         if (guiProgress != null && progressId != -1)
533         {
534           guiProgress.setProgressBar("", progressId);
535         }
536         // TODO: may not need to paintAlignment again !
537         ap.paintAlignment(false, false);
538       }
539       if (msg.length() > 0)
540       {
541         // TODO: stash message somewhere in annotation or alignment view.
542         // code below shows result in a text box popup
543         /*
544          * jalview.gui.CutAndPasteTransfer cap = new
545          * jalview.gui.CutAndPasteTransfer(); cap.setText(msg.toString());
546          * jalview.gui.Desktop.addInternalFrame(cap,
547          * "Job Status for "+getServiceActionText(), 600, 400);
548          */
549       }
550     }
551
552   }
553
554   /**
555    * validate input for dynamic/non-dynamic update context TODO: move to
556    * analysis interface ?
557    * @param seqs
558    * 
559    * @return true if input is valid
560    */
561   boolean checkValidInputSeqs(List<SequenceI> seqs)
562   {
563     int nvalid = 0;
564     for (SequenceI sq : seqs)
565     {
566       if (sq.getStart() <= sq.getEnd()
567               && (sq.isProtein() ? proteinAllowed : nucleotidesAllowed))
568       {
569         if (submitGaps
570                 || sq.getLength() == (sq.getEnd() - sq.getStart() + 1))
571         {
572           nvalid++;
573         }
574       }
575     }
576     return nvalid >= min_valid_seqs;
577   }
578
579   public void cancelCurrentJob()
580   {
581     try
582     {
583       String id = running.getJobId();
584       if (((CancellableI) annotService).cancel(running))
585       {
586         System.err.println("Cancelled job " + id);
587       }
588       else
589       {
590         System.err.println("Job " + id + " couldn't be cancelled.");
591       }
592     } catch (Exception q)
593     {
594       q.printStackTrace();
595     }
596   }
597
598   /**
599    * Interactive updating. Analysis calculations that work on the currently
600    * displayed alignment data should cancel existing jobs when the input data
601    * has changed.
602    * 
603    * @return true if a running job should be cancelled because new input data is
604    *         available for analysis
605    */
606   boolean isInteractiveUpdate()
607   {
608     return service.isInteractiveUpdate();
609   }
610
611   /**
612    * decide what sequences will be analysed TODO: refactor to generate
613    * List<SequenceI> for submission to service interface
614    * 
615    * @param alignment
616    * @param inputSeqs
617    * @return
618    */
619   public List<SequenceI> getInputSequences(AlignmentI alignment,
620           AnnotatedCollectionI inputSeqs)
621   {
622     if (alignment == null || alignment.getWidth() <= 0
623             || alignment.getSequences() == null || alignment.isNucleotide()
624                     ? !nucleotidesAllowed
625                     : !proteinAllowed)
626     {
627       return null;
628     }
629     if (inputSeqs == null || inputSeqs.getWidth() <= 0
630             || inputSeqs.getSequences() == null
631             || inputSeqs.getSequences().size() < 1)
632     {
633       inputSeqs = alignment;
634     }
635
636     List<SequenceI> seqs = new ArrayList<>();
637
638     int minlen = 10;
639     int ln = -1;
640     if (bySequence)
641     {
642       seqNames = new HashMap<>();
643     }
644     gapMap = new boolean[0];
645     start = inputSeqs.getStartRes();
646     end = inputSeqs.getEndRes();
647     // TODO: URGENT! unify with JPred / MSA code to handle hidden regions
648     // correctly
649     // TODO: push attributes into WsJob instance (so they can be safely
650     // persisted/restored
651     for (SequenceI sq : (inputSeqs.getSequences()))
652     {
653       if (bySequence
654               ? sq.findPosition(end + 1)
655                       - sq.findPosition(start + 1) > minlen - 1
656               : sq.getEnd() - sq.getStart() > minlen - 1)
657       {
658         String newname = SeqsetUtils.unique_name(seqs.size() + 1);
659         // make new input sequence with or without gaps
660         if (seqNames != null)
661         {
662           seqNames.put(newname, sq);
663         }
664         SequenceI seq;
665         if (submitGaps)
666         {
667           seqs.add(seq = new jalview.datamodel.Sequence(newname,
668                   sq.getSequenceAsString()));
669           if (gapMap == null || gapMap.length < seq.getLength())
670           {
671             boolean[] tg = gapMap;
672             gapMap = new boolean[seq.getLength()];
673             System.arraycopy(tg, 0, gapMap, 0, tg.length);
674             for (int p = tg.length; p < gapMap.length; p++)
675             {
676               gapMap[p] = false; // init as a gap
677             }
678           }
679           for (int apos : sq.gapMap())
680           {
681             char sqc = sq.getCharAt(apos);
682             if (!filterNonStandardResidues
683                     || (sq.isProtein() ? ResidueProperties.aaIndex[sqc] < 20
684                             : ResidueProperties.nucleotideIndex[sqc] < 5))
685             {
686               gapMap[apos] = true; // aligned and real amino acid residue
687             }
688             ;
689           }
690         }
691         else
692         {
693           // TODO: add ability to exclude hidden regions
694           seqs.add(seq = new jalview.datamodel.Sequence(newname,
695                   AlignSeq.extractGaps(jalview.util.Comparison.GapChars,
696                           sq.getSequenceAsString(start, end + 1))));
697           // for annotation need to also record map to sequence start/end
698           // position in range
699           // then transfer back to original sequence on return.
700         }
701         if (seq.getLength() > ln)
702         {
703           ln = seq.getLength();
704         }
705       }
706     }
707     if (alignedSeqs && submitGaps)
708     {
709       realw = 0;
710       for (int i = 0; i < gapMap.length; i++)
711       {
712         if (gapMap[i])
713         {
714           realw++;
715         }
716       }
717       // try real hard to return something submittable
718       // TODO: some of AAcon measures need a minimum of two or three amino
719       // acids at each position, and AAcon doesn't gracefully degrade.
720       for (int p = 0; p < seqs.size(); p++)
721       {
722         SequenceI sq = seqs.get(p);
723         // strip gapped columns
724         char[] padded = new char[realw],
725                 orig = sq.getSequence();
726         for (int i = 0, pp = 0; i < realw; pp++)
727         {
728           if (gapMap[pp])
729           {
730             if (orig.length > pp)
731             {
732               padded[i++] = orig[pp];
733             }
734             else
735             {
736               padded[i++] = '-';
737             }
738           }
739         }
740         seqs.set(p, new jalview.datamodel.Sequence(sq.getName(),
741                 new String(padded)));
742       }
743     }
744     return seqs;
745   }
746
747   @Override
748   public void updateAnnotation()
749   {
750     updateResultAnnotation(false);
751   }
752
753   public void updateResultAnnotation(boolean immediate)
754   {
755     if ((immediate || !calcMan.isWorking(this)) && running != null
756             && running.hasResults())
757     {
758       List<AlignmentAnnotation> ourAnnot = running.getAnnotation(),
759               newAnnots = new ArrayList<>();
760       //
761       // update graphGroup for all annotation
762       //
763       /**
764        * find a graphGroup greater than any existing ones this could be a method
765        * provided by alignment Alignment.getNewGraphGroup() - returns next
766        * unused graph group
767        */
768       int graphGroup = 1;
769       if (alignViewport.getAlignment().getAlignmentAnnotation() != null)
770       {
771         for (AlignmentAnnotation ala : alignViewport.getAlignment()
772                 .getAlignmentAnnotation())
773         {
774           if (ala.graphGroup > graphGroup)
775           {
776             graphGroup = ala.graphGroup;
777           }
778         }
779       }
780       /**
781        * update graphGroup in the annotation rows returned from service
782        */
783       // TODO: look at sequence annotation rows and update graph groups in the
784       // case of reference annotation.
785       for (AlignmentAnnotation ala : ourAnnot)
786       {
787         if (ala.graphGroup > 0)
788         {
789           ala.graphGroup += graphGroup;
790         }
791         SequenceI aseq = null;
792
793         /**
794          * transfer sequence refs and adjust gapmap
795          */
796         if (ala.sequenceRef != null)
797         {
798           SequenceI seq = running.getSeqNames()
799                   .get(ala.sequenceRef.getName());
800           aseq = seq;
801           while (seq.getDatasetSequence() != null)
802           {
803             seq = seq.getDatasetSequence();
804           }
805         }
806         Annotation[] resAnnot = ala.annotations,
807                 gappedAnnot = new Annotation[Math.max(
808                         alignViewport.getAlignment().getWidth(),
809                         gapMap.length)];
810         for (int p = 0, ap = start; ap < gappedAnnot.length; ap++)
811         {
812           if (gapMap != null && gapMap.length > ap && !gapMap[ap])
813           {
814             gappedAnnot[ap] = new Annotation("", "", ' ', Float.NaN);
815           }
816           else if (p < resAnnot.length)
817           {
818             gappedAnnot[ap] = resAnnot[p++];
819           }
820         }
821         ala.sequenceRef = aseq;
822         ala.annotations = gappedAnnot;
823         AlignmentAnnotation newAnnot = getAlignViewport().getAlignment()
824                 .updateFromOrCopyAnnotation(ala);
825         if (aseq != null)
826         {
827
828           aseq.addAlignmentAnnotation(newAnnot);
829           newAnnot.adjustForAlignment();
830
831           AlignmentAnnotationUtils.replaceAnnotationOnAlignmentWith(
832                   newAnnot, newAnnot.label, newAnnot.getCalcId());
833         }
834         newAnnots.add(newAnnot);
835
836       }
837       for (SequenceI sq : running.getSeqs())
838       {
839         if (!sq.getFeatures().hasFeatures()
840                 && (sq.getDBRefs() == null || sq.getDBRefs().size() == 0))
841         {
842           continue;
843         }
844         running.setTransferSequenceFeatures(true);
845         SequenceI seq = running.getSeqNames().get(sq.getName());
846         SequenceI dseq;
847         ContiguousI seqRange = seq.findPositions(start, end);
848
849         while ((dseq = seq).getDatasetSequence() != null)
850         {
851           seq = seq.getDatasetSequence();
852         }
853         List<ContiguousI> sourceRange = new ArrayList();
854         if (gapMap != null && gapMap.length >= end)
855         {
856           int lastcol = start, col = start;
857           do
858           {
859             if (col == end || !gapMap[col])
860             {
861               if (lastcol <= (col - 1))
862               {
863                 seqRange = seq.findPositions(lastcol, col);
864                 sourceRange.add(seqRange);
865               }
866               lastcol = col + 1;
867             }
868           } while (++col <= end);
869         }
870         else
871         {
872           sourceRange.add(seq.findPositions(start, end));
873         }
874         int i = 0;
875         int source_startend[] = new int[sourceRange.size() * 2];
876
877         for (ContiguousI range : sourceRange)
878         {
879           source_startend[i++] = range.getBegin();
880           source_startend[i++] = range.getEnd();
881         }
882         Mapping mp = new Mapping(
883                 new MapList(source_startend, new int[]
884                 { seq.getStart(), seq.getEnd() }, 1, 1));
885         dseq.transferAnnotation(sq, mp);
886
887       }
888       updateOurAnnots(newAnnots);
889     }
890   }
891
892   /**
893    * notify manager that we have started, and wait for a free calculation slot
894    * 
895    * @return true if slot is obtained and work still valid, false if another
896    *         thread has done our work for us.
897    */
898   protected boolean checkDone()
899   {
900     calcMan.notifyStart(this);
901     ap.paintAlignment(false, false);
902     while (!calcMan.notifyWorking(this))
903     {
904       if (calcMan.isWorking(this))
905       {
906         return true;
907       }
908       try
909       {
910         if (ap != null)
911         {
912           ap.paintAlignment(false, false);
913         }
914
915         Thread.sleep(200);
916       } catch (Exception ex)
917       {
918         ex.printStackTrace();
919       }
920     }
921     if (alignViewport.isClosed())
922     {
923       abortAndDestroy();
924       return true;
925     }
926     return false;
927   }
928
929   protected void updateOurAnnots(List<AlignmentAnnotation> ourAnnot)
930   {
931     List<AlignmentAnnotation> our = ourAnnots;
932     ourAnnots = ourAnnot;
933     AlignmentI alignment = alignViewport.getAlignment();
934     if (our != null)
935     {
936       if (our.size() > 0)
937       {
938         for (AlignmentAnnotation an : our)
939         {
940           if (!ourAnnots.contains(an))
941           {
942             // remove the old annotation
943             alignment.deleteAnnotation(an);
944           }
945         }
946       }
947       our.clear();
948     }
949
950     // validate rows and update Alignmment state
951     for (AlignmentAnnotation an : ourAnnots)
952     {
953       alignViewport.getAlignment().validateAnnotation(an);
954     }
955     // TODO: may need a menu refresh after this
956     // af.setMenusForViewport();
957     ap.adjustAnnotationHeight();
958
959   }
960
961   public SequenceAnnotationServiceI getService()
962   {
963     return annotService;
964   }
965
966 }