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