Merge branch 'Jalview-JS/develop' into merge_js_develop
[jalview.git] / src / jalview / io / VamsasAppDatastore.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.io;
22
23 import jalview.bin.Cache;
24 import jalview.datamodel.AlignedCodonFrame;
25 import jalview.datamodel.AlignmentAnnotation;
26 import jalview.datamodel.GraphLine;
27 import jalview.datamodel.SequenceI;
28 import jalview.gui.AlignFrame;
29 import jalview.gui.AlignViewport;
30 import jalview.gui.Desktop;
31 import jalview.gui.TreePanel;
32 import jalview.io.vamsas.Datasetsequence;
33 import jalview.io.vamsas.DatastoreItem;
34 import jalview.io.vamsas.DatastoreRegistry;
35 import jalview.io.vamsas.Rangetype;
36 import jalview.project.Jalview2XML;
37 import jalview.util.MessageManager;
38 import jalview.util.jarInputStreamProvider;
39 import jalview.viewmodel.AlignmentViewport;
40
41 import java.io.File;
42 import java.io.IOException;
43 import java.util.Enumeration;
44 import java.util.HashMap;
45 import java.util.Hashtable;
46 import java.util.IdentityHashMap;
47 import java.util.Iterator;
48 import java.util.List;
49 import java.util.Vector;
50 import java.util.jar.JarInputStream;
51 import java.util.jar.JarOutputStream;
52
53 import uk.ac.vamsas.client.IClientAppdata;
54 import uk.ac.vamsas.client.IClientDocument;
55 import uk.ac.vamsas.client.Vobject;
56 import uk.ac.vamsas.client.VorbaId;
57 import uk.ac.vamsas.objects.core.Alignment;
58 import uk.ac.vamsas.objects.core.AlignmentSequence;
59 import uk.ac.vamsas.objects.core.AlignmentSequenceAnnotation;
60 import uk.ac.vamsas.objects.core.AnnotationElement;
61 import uk.ac.vamsas.objects.core.DataSet;
62 import uk.ac.vamsas.objects.core.DataSetAnnotations;
63 import uk.ac.vamsas.objects.core.DbRef;
64 import uk.ac.vamsas.objects.core.Entry;
65 import uk.ac.vamsas.objects.core.Glyph;
66 import uk.ac.vamsas.objects.core.Local;
67 import uk.ac.vamsas.objects.core.MapType;
68 import uk.ac.vamsas.objects.core.Mapped;
69 import uk.ac.vamsas.objects.core.Property;
70 import uk.ac.vamsas.objects.core.Provenance;
71 import uk.ac.vamsas.objects.core.RangeAnnotation;
72 import uk.ac.vamsas.objects.core.RangeType;
73 import uk.ac.vamsas.objects.core.Seg;
74 import uk.ac.vamsas.objects.core.Sequence;
75 import uk.ac.vamsas.objects.core.SequenceType;
76 import uk.ac.vamsas.objects.core.VAMSAS;
77 import uk.ac.vamsas.objects.utils.Properties;
78
79 /*
80  *
81  * static {
82  * org.exolab.castor.util.LocalConfiguration.getInstance().getProperties().setProperty(
83  * "org.exolab.castor.serializer", "org.apache.xml.serialize.XMLSerilazizer"); }
84  *
85  */
86 /*
87  * TODO: check/verify consistency for vamsas sync with group associated alignment annotation
88  */
89 public class VamsasAppDatastore
90 {
91   /**
92    * Type used for general jalview generated annotation added to vamsas document
93    */
94   public static final String JALVIEW_ANNOTATION_ROW = "JalviewAnnotation";
95
96   /**
97    * AlignmentAnnotation property to indicate that values should not be
98    * interpolated
99    */
100   public static final String DISCRETE_ANNOTATION = "discrete";
101
102   /**
103    * continuous property - optional to specify that annotation should be
104    * represented as a continous graph line
105    */
106   private static final String CONTINUOUS_ANNOTATION = "continuous";
107
108   private static final String THRESHOLD = "threshold";
109
110   /**
111    * template for provenance entries written to vamsas session document
112    */
113   Entry provEntry = null;
114
115   /**
116    * Instance of the session document being synchronized with
117    */
118   IClientDocument cdoc;
119
120   /**
121    * map Vorba (vamsas object xml ref) IDs to live jalview object references
122    */
123   Hashtable vobj2jv;
124
125   /**
126    * map live jalview object references to Vorba IDs
127    */
128   IdentityHashMap jv2vobj;
129
130   /**
131    * map jalview sequence set ID (which is vorba ID for alignment) to last
132    * recorded hash value for the alignment viewport (the undo/redo hash value)
133    */
134   Hashtable alignRDHash;
135
136   public VamsasAppDatastore(IClientDocument cdoc, Hashtable vobj2jv,
137           IdentityHashMap jv2vobj, Entry provEntry, Hashtable alignRDHash)
138   {
139     this.cdoc = cdoc;
140     this.vobj2jv = vobj2jv;
141     this.jv2vobj = jv2vobj;
142     this.provEntry = provEntry;
143     this.alignRDHash = alignRDHash;
144     buildSkipList();
145   }
146
147   /**
148    * the skipList used to skip over views from Jalview Appdata's that we've
149    * already syncrhonized
150    */
151   Hashtable skipList;
152
153   private void buildSkipList()
154   {
155     skipList = new Hashtable();
156     AlignFrame[] al = Desktop.getAlignFrames();
157     for (int f = 0; al != null && f < al.length; f++)
158     {
159       skipList.put(al[f].getViewport().getSequenceSetId(), al[f]);
160     }
161   }
162
163   /**
164    * @return the Vobject bound to Jalview datamodel object
165    */
166   protected Vobject getjv2vObj(Object jvobj)
167   {
168     if (jv2vobj.containsKey(jvobj))
169     {
170       return cdoc.getObject((VorbaId) jv2vobj.get(jvobj));
171     }
172     // check if we're working with a string - then workaround
173     // the use of IdentityHashTable because different strings
174     // have different object IDs.
175     if (jvobj instanceof String)
176     {
177       Object seqsetidobj = null;
178       seqsetidobj = getVamsasObjectBinding().get(jvobj);
179       if (seqsetidobj != null)
180       {
181         if (seqsetidobj instanceof String)
182         {
183           // what is expected. object returned by av.getSequenceSetId() -
184           // reverse lookup to get the 'registered' instance of this string
185           Vobject obj = getjv2vObj(seqsetidobj);
186           if (obj != null && !(obj instanceof Alignment))
187           {
188             Cache.log.warn(
189                     "IMPLEMENTATION ERROR?: Unexpected mapping for unmapped jalview string object content:"
190                             + seqsetidobj + " to object " + obj);
191           }
192           return obj;
193         }
194         else
195         {
196           Cache.log.warn("Unexpected mapping for Jalview String Object ID "
197                   + seqsetidobj + " to another jalview dataset object "
198                   + seqsetidobj);
199         }
200       }
201     }
202
203     if (Cache.log.isDebugEnabled())
204     {
205       Cache.log.debug(
206               "Returning null VorbaID binding for jalview object " + jvobj);
207     }
208     return null;
209   }
210
211   /**
212    * 
213    * @param vobj
214    * @return Jalview datamodel object bound to the vamsas document object
215    */
216   protected Object getvObj2jv(uk.ac.vamsas.client.Vobject vobj)
217   {
218     VorbaId id = vobj.getVorbaId();
219     if (id == null)
220     {
221       id = cdoc.registerObject(vobj);
222       Cache.log.debug(
223               "Registering new object and returning null for getvObj2jv");
224       return null;
225     }
226     if (vobj2jv.containsKey(vobj.getVorbaId()))
227     {
228       return vobj2jv.get(vobj.getVorbaId());
229     }
230     return null;
231   }
232
233   protected void bindjvvobj(Object jvobj, uk.ac.vamsas.client.Vobject vobj)
234   {
235     VorbaId id = vobj.getVorbaId();
236     if (id == null)
237     {
238       id = cdoc.registerObject(vobj);
239       if (id == null || vobj.getVorbaId() == null
240               || cdoc.getObject(id) != vobj)
241       {
242         Cache.log.error("Failed to get id for "
243                 + (vobj.isRegisterable() ? "registerable"
244                         : "unregisterable")
245                 + " object " + vobj);
246       }
247     }
248
249     if (vobj2jv.containsKey(vobj.getVorbaId())
250             && !((VorbaId) vobj2jv.get(vobj.getVorbaId())).equals(jvobj))
251     {
252       Cache.log.debug(
253               "Warning? Overwriting existing vamsas id binding for "
254                       + vobj.getVorbaId(),
255               new Exception(MessageManager.getString(
256                       "exception.overwriting_vamsas_id_binding")));
257     }
258     else if (jv2vobj.containsKey(jvobj)
259             && !((VorbaId) jv2vobj.get(jvobj)).equals(vobj.getVorbaId()))
260     {
261       Cache.log.debug(
262               "Warning? Overwriting existing jalview object binding for "
263                       + jvobj,
264               new Exception("Overwriting jalview object binding."));
265     }
266     /*
267      * Cache.log.error("Attempt to make conflicting object binding! "+vobj+" id "
268      * +vobj.getVorbaId()+" already bound to "+getvObj2jv(vobj)+" and "+jvobj+"
269      * already bound to "+getjv2vObj(jvobj),new Exception("Excessive call to
270      * bindjvvobj")); }
271      */
272     // we just update the hash's regardless!
273     Cache.log.debug("Binding " + vobj.getVorbaId() + " to " + jvobj);
274     vobj2jv.put(vobj.getVorbaId(), jvobj);
275     // JBPNote - better implementing a hybrid invertible hash.
276     jv2vobj.put(jvobj, vobj.getVorbaId());
277   }
278
279   /**
280    * put the alignment viewed by AlignViewport into cdoc.
281    * 
282    * @param av
283    *          alignViewport to be stored
284    * @param aFtitle
285    *          title for alignment
286    * @return true if alignment associated with viewport was stored/synchronized
287    *         to document
288    */
289   public boolean storeVAMSAS(AlignViewport av, String aFtitle)
290   {
291     try
292     {
293       jalview.datamodel.AlignmentI jal = av.getAlignment();
294       jalview.datamodel.AlignmentI jds = jal.getDataset();
295       boolean nw = false;
296       VAMSAS root = null; // will be resolved based on Dataset Parent.
297       // /////////////////////////////////////////
298       // SAVE THE DATASET
299       DataSet dataset = null;
300       if (jds == null)
301       {
302         Cache.log.warn("Creating new dataset for an alignment.");
303         jal.setDataset(null);
304         jds = jal.getDataset();
305       }
306
307       // try and get alignment and association for sequence set id
308
309       Alignment alignment = (Alignment) getjv2vObj(av.getSequenceSetId());
310       if (alignment != null)
311       {
312         dataset = (DataSet) alignment.getV_parent();
313       }
314       else
315       {
316         // is the dataset already registered
317         dataset = (DataSet) getjv2vObj(jds);
318       }
319
320       if (dataset == null)
321       {
322         // it might be that one of the dataset sequences does actually have a
323         // binding, so search for it indirectly. If it does, then the local
324         // jalview dataset
325         // must be merged with the existing vamsas dataset.
326         jalview.datamodel.SequenceI[] jdatset = jds.getSequencesArray();
327         for (int i = 0; i < jdatset.length; i++)
328         {
329           Vobject vbound = getjv2vObj(jdatset[i]);
330           if (vbound != null)
331           {
332             if (vbound instanceof uk.ac.vamsas.objects.core.Sequence)
333             {
334               if (dataset == null)
335               {
336                 dataset = (DataSet) vbound.getV_parent();
337               }
338               else
339               {
340                 if (vbound.getV_parent() != null
341                         && dataset != vbound.getV_parent())
342                 {
343                   throw new Error(MessageManager.getString(
344                           "error.implementation_error_cannot_map_alignment_sequences"));
345                   // This occurs because the dataset for the alignment we are
346                   // trying to
347                 }
348               }
349             }
350           }
351         }
352       }
353
354       if (dataset == null)
355       {
356         Cache.log.warn("Creating new vamsas dataset for alignment view "
357                 + av.getSequenceSetId());
358         // we create a new dataset on the default vamsas root.
359         root = cdoc.getVamsasRoots()[0]; // default vamsas root for modifying.
360         dataset = new DataSet();
361         root.addDataSet(dataset);
362         bindjvvobj(jds, dataset);
363         dataset.setProvenance(dummyProvenance());
364         // dataset.getProvenance().addEntry(provEntry);
365         nw = true;
366       }
367       else
368       {
369         root = (VAMSAS) dataset.getV_parent();
370       }
371       // update dataset
372       Sequence sequence;
373       // set new dataset and alignment sequences based on alignment Nucleotide
374       // flag.
375       // this *will* break when alignment contains both nucleotide and amino
376       // acid sequences.
377       String dict = jal.isNucleotide()
378               ? uk.ac.vamsas.objects.utils.SymbolDictionary.STANDARD_NA
379               : uk.ac.vamsas.objects.utils.SymbolDictionary.STANDARD_AA;
380       Vector dssmods = new Vector();
381       for (int i = 0; i < jal.getHeight(); i++)
382       {
383         SequenceI sq = jal.getSequenceAt(i).getDatasetSequence(); // only insert
384         // referenced
385         // sequences
386         // to dataset.
387         Datasetsequence dssync = new jalview.io.vamsas.Datasetsequence(this,
388                 sq, dict, dataset);
389         sequence = (Sequence) dssync.getVobj();
390         if (dssync.getModified())
391         {
392           dssmods.addElement(sequence);
393         }
394         ;
395       }
396       if (dssmods.size() > 0)
397       {
398         if (!nw)
399         {
400           Entry pentry = this.addProvenance(dataset.getProvenance(),
401                   "updated sequences");
402           // pentry.addInput(vInput); could write in which sequences were
403           // modified.
404           dssmods.removeAllElements();
405         }
406       }
407       // dataset.setProvenance(getVamsasProvenance(jal.getDataset().getProvenance()));
408       // ////////////////////////////////////////////
409       if (alignmentWillBeSkipped(av))
410       {
411         // TODO: trees could be written - but for the moment we just
412         addToSkipList(av);
413         // add to the JalviewXML skipList and ..
414         return false;
415       }
416
417       if (alignment == null)
418       {
419         alignment = new Alignment();
420         bindjvvobj(av.getSequenceSetId(), alignment);
421         if (alignment.getProvenance() == null)
422         {
423           alignment.setProvenance(new Provenance());
424         }
425         addProvenance(alignment.getProvenance(), "added"); // TODO: insert some
426         // sensible source
427         // here
428         dataset.addAlignment(alignment);
429         {
430           Property title = new Property();
431           title.setName("title");
432           title.setType("string");
433           title.setContent(aFtitle);
434           alignment.addProperty(title);
435         }
436         alignment.setGapChar(String.valueOf(av.getGapCharacter()));
437         for (int i = 0; i < jal.getHeight(); i++)
438         {
439           syncToAlignmentSequence(jal.getSequenceAt(i), alignment, null);
440         }
441         alignRDHash.put(av.getSequenceSetId(), av.getUndoRedoHash());
442       }
443       else
444       {
445         // always prepare to clone the alignment
446         boolean alismod = av.isUndoRedoHashModified(
447                 (long[]) alignRDHash.get(av.getSequenceSetId()));
448         // todo: verify and update mutable alignment props.
449         // TODO: Use isLocked methods
450         if (alignment.getModifiable() == null
451                 || alignment.getModifiable().length() == 0)
452         // && !alignment.isDependedOn())
453         {
454           boolean modified = false;
455           // check existing sequences in local and in document.
456           Vector docseqs = new Vector(
457                   alignment.getAlignmentSequenceAsReference());
458           for (int i = 0; i < jal.getHeight(); i++)
459           {
460             modified |= syncToAlignmentSequence(jal.getSequenceAt(i),
461                     alignment, docseqs);
462           }
463           if (docseqs.size() > 0)
464           {
465             // removeValignmentSequences(alignment, docseqs);
466             docseqs.removeAllElements();
467             System.out.println(
468                     "Sequence deletion from alignment is not implemented.");
469
470           }
471           if (modified)
472           {
473             if (alismod)
474             {
475               // info in the undo
476               addProvenance(alignment.getProvenance(), "Edited"); // TODO:
477               // insert
478               // something
479               // sensible
480               // here again
481             }
482             else
483             {
484               // info in the undo
485               addProvenance(alignment.getProvenance(), "Attributes Edited"); // TODO:
486               // insert
487               // something
488               // sensible
489               // here
490               // again
491             }
492           }
493           if (alismod)
494           {
495             System.out.println("update alignment in document.");
496           }
497           else
498           {
499             System.out.println("alignment in document left unchanged.");
500           }
501         }
502         else
503         {
504           // unbind alignment from view.
505           // create new binding and new alignment.
506           // mark trail on new alignment as being derived from old ?
507           System.out.println(
508                   "update edited alignment to new alignment in document.");
509         }
510       }
511       // ////////////////////////////////////////////
512       // SAVE Alignment Sequence Features
513       for (int i = 0, iSize = alignment
514               .getAlignmentSequenceCount(); i < iSize; i++)
515       {
516         AlignmentSequence valseq;
517         SequenceI alseq = (SequenceI) getvObj2jv(
518                 valseq = alignment.getAlignmentSequence(i));
519         if (alseq != null && alseq.getSequenceFeatures() != null)
520         {
521           /*
522            * We do not put local Alignment Sequence Features into the vamsas
523            * document yet.
524            * 
525            * 
526            * jalview.datamodel.SequenceFeature[] features = alseq
527            * .getSequenceFeatures(); for (int f = 0; f < features.length; f++) {
528            * if (features[f] != null) { AlignmentSequenceAnnotation valseqf = (
529            * AlignmentSequenceAnnotation) getjv2vObj(features[i]); if (valseqf
530            * == null) {
531            * 
532            * valseqf = (AlignmentSequenceAnnotation) getDSAnnotationFromJalview(
533            * new AlignmentSequenceAnnotation(), features[i]);
534            * valseqf.setGraph(false);
535            * valseqf.addProperty(newProperty("jalview:feature"
536            * ,"boolean","true")); if (valseqf.getProvenance() == null) {
537            * valseqf.setProvenance(new Provenance()); }
538            * addProvenance(valseqf.getProvenance(), "created"); // JBPNote - //
539            * need to // update bindjvvobj(features[i], valseqf);
540            * valseq.addAlignmentSequenceAnnotation(valseqf); } } }
541            */
542         }
543       }
544
545       // ////////////////////////////////////////////
546       // SAVE ANNOTATIONS
547       if (jal.getAlignmentAnnotation() != null)
548       {
549         jalview.datamodel.AlignmentAnnotation[] aa = jal
550                 .getAlignmentAnnotation();
551         java.util.HashMap AlSeqMaps = new HashMap(); // stores int maps from
552         // alignment columns to
553         // sequence positions.
554         for (int i = 0; i < aa.length; i++)
555         {
556           if (aa[i] == null || isJalviewOnly(aa[i]))
557           {
558             continue;
559           }
560           if (aa[i].groupRef != null)
561           {
562             // TODO: store any group associated annotation references
563             Cache.log.warn(
564                     "Group associated sequence annotation is not stored in VAMSAS document.");
565             continue;
566           }
567           if (aa[i].sequenceRef != null)
568           {
569             // Deal with sequence associated annotation
570             Vobject sref = getjv2vObj(aa[i].sequenceRef);
571             if (sref instanceof uk.ac.vamsas.objects.core.AlignmentSequence)
572             {
573               saveAlignmentSequenceAnnotation(AlSeqMaps,
574                       (AlignmentSequence) sref, aa[i]);
575             }
576             else
577             {
578               // first find the alignment sequence to associate this with.
579               for (SequenceI jvalsq : av.getAlignment().getSequences())
580               {
581                 // saveDatasetSequenceAnnotation(AlSeqMaps,(uk.ac.vamsas.objects.core.Sequence)
582                 // sref, aa[i]);
583                 if (jvalsq.getDatasetSequence() == aa[i].sequenceRef)
584                 {
585                   Vobject alsref = getjv2vObj(jvalsq);
586                   saveAlignmentSequenceAnnotation(AlSeqMaps,
587                           (AlignmentSequence) alsref, aa[i]);
588                   break;
589                 }
590                 ;
591               }
592             }
593           }
594           else
595           {
596             // add Alignment Annotation
597             uk.ac.vamsas.objects.core.AlignmentAnnotation an = (uk.ac.vamsas.objects.core.AlignmentAnnotation) getjv2vObj(
598                     aa[i]);
599             if (an == null)
600             {
601               an = new uk.ac.vamsas.objects.core.AlignmentAnnotation();
602               an.setType(JALVIEW_ANNOTATION_ROW);
603               an.setDescription(aa[i].description);
604               alignment.addAlignmentAnnotation(an);
605               Seg vSeg = new Seg(); // TODO: refactor to have a default
606               // rangeAnnotationType initer/updater that
607               // takes a set of int ranges.
608               vSeg.setStart(1);
609               vSeg.setInclusive(true);
610               vSeg.setEnd(jal.getWidth());
611               an.addSeg(vSeg);
612               if (aa[i].graph > 0)
613               {
614                 an.setGraph(true); // aa[i].graph);
615               }
616               an.setLabel(aa[i].label);
617               an.setProvenance(dummyProvenance());
618               if (aa[i].graph != AlignmentAnnotation.NO_GRAPH)
619               {
620                 an.setGroup(Integer.toString(aa[i].graphGroup)); // // JBPNote
621                 // -
622                 // originally we
623                 // were going to
624                 // store
625                 // graphGroup in
626                 // the Jalview
627                 // specific
628                 // bits.
629                 an.setGraph(true);
630               }
631               else
632               {
633                 an.setGraph(false);
634               }
635               AnnotationElement ae;
636
637               for (int a = 0; a < aa[i].annotations.length; a++)
638               {
639                 if ((aa[i] == null) || (aa[i].annotations[a] == null))
640                 {
641                   continue;
642                 }
643
644                 ae = new AnnotationElement();
645                 ae.setDescription(aa[i].annotations[a].description);
646                 ae.addGlyph(new Glyph());
647                 ae.getGlyph(0)
648                         .setContent(aa[i].annotations[a].displayCharacter); // assume
649                 // jax-b
650                 // takes
651                 // care
652                 // of
653                 // utf8
654                 // translation
655                 if (an.isGraph())
656                 {
657                   ae.addValue(aa[i].annotations[a].value);
658                 }
659                 ae.setPosition(a + 1);
660                 if (aa[i].annotations[a].secondaryStructure != ' ')
661                 {
662                   Glyph ss = new Glyph();
663                   ss.setDict(
664                           uk.ac.vamsas.objects.utils.GlyphDictionary.PROTEIN_SS_3STATE);
665                   ss.setContent(String.valueOf(
666                           aa[i].annotations[a].secondaryStructure));
667                   ae.addGlyph(ss);
668                 }
669                 an.addAnnotationElement(ae);
670               }
671               if (aa[i].editable)
672               {
673                 // an.addProperty(newProperty("jalview:editable", null,
674                 // "true"));
675                 // an.setModifiable(""); // TODO: This is not the way the
676                 // modifiable flag is supposed to be used.
677               }
678               setAnnotationType(an, aa[i]);
679
680               if (aa[i].graph != jalview.datamodel.AlignmentAnnotation.NO_GRAPH)
681               {
682                 an.setGraph(true);
683                 an.setGroup(Integer.toString(aa[i].graphGroup));
684                 if (aa[i].threshold != null && aa[i].threshold.displayed)
685                 {
686                   an.addProperty(Properties.newProperty(THRESHOLD,
687                           Properties.FLOATTYPE,
688                           "" + aa[i].threshold.value));
689                   if (aa[i].threshold.label != null)
690                   {
691                     an.addProperty(Properties.newProperty(
692                             THRESHOLD + "Name", Properties.STRINGTYPE,
693                             "" + aa[i].threshold.label));
694                   }
695                 }
696               }
697
698             }
699
700             else
701             {
702               if (an.getModifiable() == null) // TODO: USE VAMSAS LIBRARY OBJECT
703               // LOCK METHODS)
704               {
705                 // verify annotation - update (perhaps)
706                 Cache.log.info(
707                         "update alignment sequence annotation. not yet implemented.");
708               }
709               else
710               {
711                 // verify annotation - update (perhaps)
712                 Cache.log.info(
713                         "updated alignment sequence annotation added.");
714               }
715             }
716           }
717         }
718       }
719       // /////////////////////////////////////////////////////
720
721       // //////////////////////////////////////////////
722       // /SAVE THE TREES
723       // /////////////////////////////////
724       // FIND ANY ASSOCIATED TREES
725       if (Desktop.getDesktopPane() != null)
726       {
727         javax.swing.JInternalFrame[] frames = Desktop.getInstance()
728                 .getAllFrames();
729
730         for (int t = 0; t < frames.length; t++)
731         {
732           if (frames[t] instanceof TreePanel)
733           {
734             TreePanel tp = (TreePanel) frames[t];
735
736             if (tp.getViewPort().getSequenceSetId()
737                     .equals(av.getSequenceSetId()))
738             {
739               DatastoreItem vtree = new jalview.io.vamsas.Tree(this, tp,
740                       jal, alignment);
741             }
742           }
743         }
744       }
745       // Store Jalview specific stuff in the Jalview appData
746       // not implemented in the SimpleDoc interface.
747     }
748
749     catch (Exception ex)
750     {
751       ex.printStackTrace();
752       return false;
753     }
754     return true;
755   }
756
757   /**
758    * very quick test to see if the viewport would be stored in the vamsas
759    * document. Reasons for not storing include the unaligned flag being false
760    * (for all sequences, including the hidden ones!)
761    * 
762    * @param av
763    * @return true if alignment associated with this view will be stored in
764    *         document.
765    */
766   public boolean alignmentWillBeSkipped(AlignmentViewport av)
767   {
768     return (!av.getAlignment().isAligned());
769   }
770
771   private void addToSkipList(AlignmentViewport av)
772   {
773     if (skipList == null)
774     {
775       skipList = new Hashtable();
776     }
777     skipList.put(av.getSequenceSetId(), av);
778   }
779
780   /**
781    * remove docseqs from the given alignment marking provenance appropriately
782    * and removing any references to the sequences.
783    * 
784    * @param alignment
785    * @param docseqs
786    */
787   private void removeValignmentSequences(Alignment alignment,
788           Vector docseqs)
789   {
790     // delete these from document. This really needs to be a generic document
791     // API function derived by CASTOR.
792     Enumeration en = docseqs.elements();
793     while (en.hasMoreElements())
794     {
795       alignment.removeAlignmentSequence(
796               (AlignmentSequence) en.nextElement());
797     }
798     Entry pe = addProvenance(alignment.getProvenance(),
799             "Removed " + docseqs.size() + " sequences");
800     en = alignment.enumerateAlignmentAnnotation();
801     Vector toremove = new Vector();
802     while (en.hasMoreElements())
803     {
804       uk.ac.vamsas.objects.core.AlignmentAnnotation alan = (uk.ac.vamsas.objects.core.AlignmentAnnotation) en
805               .nextElement();
806       if (alan.getSeqrefsCount() > 0)
807       {
808         int p = 0;
809         Vector storem = new Vector();
810         Enumeration sr = alan.enumerateSeqrefs();
811         while (sr.hasMoreElements())
812         {
813           Object alsr = sr.nextElement();
814           if (docseqs.contains(alsr))
815           {
816             storem.addElement(alsr);
817           }
818         }
819         // remove references to the deleted sequences
820         sr = storem.elements();
821         while (sr.hasMoreElements())
822         {
823           alan.removeSeqrefs(sr.nextElement());
824         }
825
826         if (alan.getSeqrefsCount() == 0)
827         {
828           // should then delete alan from dataset
829           toremove.addElement(alan);
830         }
831       }
832     }
833     // remove any annotation that used to be associated to a specific bunch of
834     // sequences
835     en = toremove.elements();
836     while (en.hasMoreElements())
837     {
838       alignment.removeAlignmentAnnotation(
839               (uk.ac.vamsas.objects.core.AlignmentAnnotation) en
840                       .nextElement());
841     }
842     // TODO: search through alignment annotations to remove any references to
843     // this alignment sequence
844   }
845
846   /**
847    * sync a jalview alignment seuqence into a vamsas alignment assumes all lock
848    * transformation/bindings have been sorted out before hand. creates/syncs the
849    * vamsas alignment sequence for jvalsq and adds it to the alignment if
850    * necessary. unbounddocseq is a duplicate of the vamsas alignment sequences
851    * and these are removed after being processed w.r.t a bound jvalsq
852    * 
853    */
854   private boolean syncToAlignmentSequence(SequenceI jvalsq,
855           Alignment alignment, Vector unbounddocseq)
856   {
857     boolean modal = false;
858     // todo: islocked method here
859     boolean up2doc = false;
860     AlignmentSequence alseq = (AlignmentSequence) getjv2vObj(jvalsq);
861     if (alseq == null)
862     {
863       alseq = new AlignmentSequence();
864       up2doc = true;
865     }
866     else
867     {
868       if (unbounddocseq != null)
869       {
870         unbounddocseq.removeElement(alseq);
871       }
872     }
873     // boolean locked = (alignment.getModifiable()==null ||
874     // alignment.getModifiable().length()>0);
875     // TODO: VAMSAS: translate lowercase symbols to annotation ?
876     if (up2doc || !alseq.getSequence().equals(jvalsq.getSequenceAsString()))
877     {
878       alseq.setSequence(jvalsq.getSequenceAsString());
879       alseq.setStart(jvalsq.getStart());
880       alseq.setEnd(jvalsq.getEnd());
881       modal = true;
882     }
883     if (up2doc || !alseq.getName().equals(jvalsq.getName()))
884     {
885       modal = true;
886       alseq.setName(jvalsq.getName());
887     }
888     if (jvalsq.getDescription() != null && (alseq.getDescription() == null
889             || !jvalsq.getDescription().equals(alseq.getDescription())))
890     {
891       modal = true;
892       alseq.setDescription(jvalsq.getDescription());
893     }
894     if (getjv2vObj(jvalsq.getDatasetSequence()) == null)
895     {
896       Cache.log.warn(
897               "Serious Implementation error - Unbound dataset sequence in alignment: "
898                       + jvalsq.getDatasetSequence());
899     }
900     alseq.setRefid(getjv2vObj(jvalsq.getDatasetSequence()));
901     if (up2doc)
902     {
903
904       alignment.addAlignmentSequence(alseq);
905       bindjvvobj(jvalsq, alseq);
906     }
907     return up2doc || modal;
908   }
909
910   /**
911    * locally sync a jalview alignment seuqence from a vamsas alignment assumes
912    * all lock transformation/bindings have been sorted out before hand.
913    * creates/syncs the jvalsq from the alignment sequence
914    */
915   private boolean syncFromAlignmentSequence(AlignmentSequence valseq,
916           char valGapchar, char gapChar, List<SequenceI> dsseqs)
917
918   {
919     boolean modal = false;
920     // todo: islocked method here
921     boolean upFromdoc = false;
922     jalview.datamodel.SequenceI alseq = (SequenceI) getvObj2jv(valseq);
923     if (alseq == null)
924     {
925       upFromdoc = true;
926     }
927     if (alseq != null)
928     {
929
930       // boolean locked = (alignment.getModifiable()==null ||
931       // alignment.getModifiable().length()>0);
932       // TODO: VAMSAS: translate lowercase symbols to annotation ?
933       if (upFromdoc
934               || !valseq.getSequence().equals(alseq.getSequenceAsString()))
935       {
936         // this might go *horribly* wrong
937         alseq.setSequence(new String(valseq.getSequence())
938                 .replace(valGapchar, gapChar));
939         alseq.setStart((int) valseq.getStart());
940         alseq.setEnd((int) valseq.getEnd());
941         modal = true;
942       }
943       if (!valseq.getName().equals(alseq.getName()))
944       {
945         modal = true;
946         alseq.setName(valseq.getName());
947       }
948       if (alseq.getDescription() == null || (valseq.getDescription() != null
949               && !alseq.getDescription().equals(valseq.getDescription())))
950       {
951         alseq.setDescription(valseq.getDescription());
952         modal = true;
953       }
954       if (modal && Cache.log.isDebugEnabled())
955       {
956         Cache.log.debug(
957                 "Updating apparently edited sequence " + alseq.getName());
958       }
959     }
960     else
961     {
962       alseq = new jalview.datamodel.Sequence(valseq.getName(),
963               valseq.getSequence().replace(valGapchar, gapChar),
964               (int) valseq.getStart(), (int) valseq.getEnd());
965
966       Vobject datsetseq = (Vobject) valseq.getRefid();
967       if (datsetseq != null)
968       {
969         alseq.setDatasetSequence((SequenceI) getvObj2jv(datsetseq)); // exceptions
970         if (valseq.getDescription() != null)
971         {
972           alseq.setDescription(valseq.getDescription());
973         }
974         else
975         {
976           // inherit description line from dataset.
977           if (alseq.getDatasetSequence().getDescription() != null)
978           {
979             alseq.setDescription(
980                     alseq.getDatasetSequence().getDescription());
981           }
982         }
983         // if
984         // AlignemntSequence
985         // reference
986         // isn't
987         // a
988         // simple
989         // SequenceI
990       }
991       else
992       {
993         Cache.log.error(
994                 "Invalid dataset sequence id (null) for alignment sequence "
995                         + valseq.getVorbaId());
996       }
997       bindjvvobj(alseq, valseq);
998       alseq.setVamsasId(valseq.getVorbaId().getId());
999       dsseqs.add(alseq);
1000     }
1001     Vobject datsetseq = (Vobject) valseq.getRefid();
1002     if (datsetseq != null)
1003     {
1004       if (datsetseq != alseq.getDatasetSequence())
1005       {
1006         modal = true;
1007       }
1008       alseq.setDatasetSequence((SequenceI) getvObj2jv(datsetseq)); // exceptions
1009     }
1010     return upFromdoc || modal;
1011   }
1012
1013   private void initRangeAnnotationType(RangeAnnotation an,
1014           AlignmentAnnotation alan, int[] gapMap)
1015   {
1016     Seg vSeg = new Seg();
1017     vSeg.setStart(1);
1018     vSeg.setInclusive(true);
1019     vSeg.setEnd(gapMap.length);
1020     an.addSeg(vSeg);
1021
1022     // LATER: much of this is verbatim from the alignmentAnnotation
1023     // method below. suggests refactoring to make rangeAnnotation the
1024     // base class
1025     an.setDescription(alan.description);
1026     an.setLabel(alan.label);
1027     an.setGroup(Integer.toString(alan.graphGroup));
1028     // // JBPNote -
1029     // originally we
1030     // were going to
1031     // store
1032     // graphGroup in
1033     // the Jalview
1034     // specific
1035     // bits.
1036     AnnotationElement ae;
1037     for (int a = 0; a < alan.annotations.length; a++)
1038     {
1039       if (alan.annotations[a] == null)
1040       {
1041         continue;
1042       }
1043
1044       ae = new AnnotationElement();
1045       ae.setDescription(alan.annotations[a].description);
1046       ae.addGlyph(new Glyph());
1047       ae.getGlyph(0).setContent(alan.annotations[a].displayCharacter); // assume
1048       // jax-b
1049       // takes
1050       // care
1051       // of
1052       // utf8
1053       // translation
1054       if (alan.graph != jalview.datamodel.AlignmentAnnotation.NO_GRAPH)
1055       {
1056         ae.addValue(alan.annotations[a].value);
1057       }
1058       ae.setPosition(gapMap[a] + 1); // position w.r.t. AlignmentSequence
1059       // symbols
1060       if (alan.annotations[a].secondaryStructure != ' ')
1061       {
1062         // we only write an annotation where it really exists.
1063         Glyph ss = new Glyph();
1064         ss.setDict(
1065                 uk.ac.vamsas.objects.utils.GlyphDictionary.PROTEIN_SS_3STATE);
1066         ss.setContent(
1067                 String.valueOf(alan.annotations[a].secondaryStructure));
1068         ae.addGlyph(ss);
1069       }
1070       an.addAnnotationElement(ae);
1071     }
1072
1073   }
1074
1075   private void saveDatasetSequenceAnnotation(HashMap AlSeqMaps,
1076           uk.ac.vamsas.objects.core.Sequence sref, AlignmentAnnotation alan)
1077   {
1078     // {
1079     // uk.ac.vamsas.
1080     // objects.core.AlignmentSequence alsref = (uk.ac.vamsas.
1081     // objects.core.AlignmentSequence) sref;
1082     uk.ac.vamsas.objects.core.DataSetAnnotations an = (uk.ac.vamsas.objects.core.DataSetAnnotations) getjv2vObj(
1083             alan);
1084     int[] gapMap = getGapMap(AlSeqMaps, alan);
1085     if (an == null)
1086     {
1087       an = new uk.ac.vamsas.objects.core.DataSetAnnotations();
1088       initRangeAnnotationType(an, alan, gapMap);
1089
1090       an.setProvenance(dummyProvenance()); // get provenance as user
1091       // created, or jnet, or
1092       // something else.
1093       setAnnotationType(an, alan);
1094       an.setGroup(Integer.toString(alan.graphGroup)); // // JBPNote -
1095       // originally we
1096       // were going to
1097       // store
1098       // graphGroup in
1099       // the Jalview
1100       // specific
1101       // bits.
1102       if (alan.getThreshold() != null && alan.getThreshold().displayed)
1103       {
1104         an.addProperty(Properties.newProperty(THRESHOLD,
1105                 Properties.FLOATTYPE, "" + alan.getThreshold().value));
1106         if (alan.getThreshold().label != null)
1107         {
1108           an.addProperty(Properties.newProperty(THRESHOLD + "Name",
1109                   Properties.STRINGTYPE, "" + alan.getThreshold().label));
1110         }
1111       }
1112       ((DataSet) sref.getV_parent()).addDataSetAnnotations(an);
1113       bindjvvobj(alan, an);
1114     }
1115     else
1116     {
1117       // update reference sequence Annotation
1118       if (an.getModifiable() == null) // TODO: USE VAMSAS LIBRARY OBJECT LOCK
1119       // METHODS)
1120       {
1121         // verify existing alignment sequence annotation is up to date
1122         System.out.println("update dataset sequence annotation.");
1123       }
1124       else
1125       {
1126         // verify existing alignment sequence annotation is up to date
1127         System.out.println(
1128                 "make new alignment dataset sequence annotation if modification has happened.");
1129       }
1130     }
1131
1132   }
1133
1134   private int[] getGapMap(HashMap AlSeqMaps, AlignmentAnnotation alan)
1135   {
1136     int[] gapMap;
1137     if (AlSeqMaps.containsKey(alan.sequenceRef))
1138     {
1139       gapMap = (int[]) AlSeqMaps.get(alan.sequenceRef);
1140     }
1141     else
1142     {
1143       gapMap = new int[alan.sequenceRef.getLength()];
1144       // map from alignment position to sequence position.
1145       int[] sgapMap = alan.sequenceRef.gapMap();
1146       for (int a = 0; a < sgapMap.length; a++)
1147       {
1148         gapMap[sgapMap[a]] = a;
1149       }
1150     }
1151     return gapMap;
1152   }
1153
1154   private void saveAlignmentSequenceAnnotation(HashMap AlSeqMaps,
1155           AlignmentSequence alsref, AlignmentAnnotation alan)
1156   {
1157     // {
1158     // uk.ac.vamsas.
1159     // objects.core.AlignmentSequence alsref = (uk.ac.vamsas.
1160     // objects.core.AlignmentSequence) sref;
1161     uk.ac.vamsas.objects.core.AlignmentSequenceAnnotation an = (uk.ac.vamsas.objects.core.AlignmentSequenceAnnotation) getjv2vObj(
1162             alan);
1163     int[] gapMap = getGapMap(AlSeqMaps, alan);
1164     if (an == null)
1165     {
1166       an = new uk.ac.vamsas.objects.core.AlignmentSequenceAnnotation();
1167       initRangeAnnotationType(an, alan, gapMap);
1168       /**
1169        * I mean here that we don't actually have a semantic 'type' for the
1170        * annotation (this might be - score, intrinsic property, measurement,
1171        * something extracted from another program, etc)
1172        */
1173       an.setType(JALVIEW_ANNOTATION_ROW); // TODO: better fix
1174       // this rough guess ;)
1175       alsref.addAlignmentSequenceAnnotation(an);
1176       bindjvvobj(alan, an);
1177       // These properties are directly supported by the
1178       // AlignmentSequenceAnnotation type.
1179       setAnnotationType(an, alan);
1180       an.setProvenance(dummyProvenance()); // get provenance as user
1181       // created, or jnet, or
1182       // something else.
1183     }
1184     else
1185     {
1186       // update reference sequence Annotation
1187       if (an.getModifiable() == null) // TODO: USE VAMSAS LIBRARY OBJECT LOCK
1188       // METHODS)
1189       {
1190         // verify existing alignment sequence annotation is up to date
1191         System.out.println("update alignment sequence annotation.");
1192       }
1193       else
1194       {
1195         // verify existing alignment sequence annotation is up to date
1196         System.out.println(
1197                 "make new alignment sequence annotation if modification has happened.");
1198       }
1199     }
1200   }
1201
1202   /**
1203    * set vamsas annotation object type from jalview annotation
1204    * 
1205    * @param an
1206    * @param alan
1207    */
1208   private void setAnnotationType(RangeAnnotation an,
1209           AlignmentAnnotation alan)
1210   {
1211     if (an instanceof AlignmentSequenceAnnotation)
1212     {
1213       if (alan.graph != AlignmentAnnotation.NO_GRAPH)
1214       {
1215         ((AlignmentSequenceAnnotation) an).setGraph(true);
1216       }
1217       else
1218       {
1219         ((AlignmentSequenceAnnotation) an).setGraph(false);
1220       }
1221     }
1222     if (an instanceof uk.ac.vamsas.objects.core.AlignmentAnnotation)
1223     {
1224       if (alan.graph != AlignmentAnnotation.NO_GRAPH)
1225       {
1226         ((uk.ac.vamsas.objects.core.AlignmentAnnotation) an).setGraph(true);
1227       }
1228       else
1229       {
1230         ((uk.ac.vamsas.objects.core.AlignmentAnnotation) an)
1231                 .setGraph(false);
1232       }
1233     }
1234     switch (alan.graph)
1235     {
1236     case AlignmentAnnotation.BAR_GRAPH:
1237       an.addProperty(Properties.newProperty(DISCRETE_ANNOTATION,
1238               Properties.BOOLEANTYPE, "true"));
1239       break;
1240     case AlignmentAnnotation.LINE_GRAPH:
1241       an.addProperty(Properties.newProperty(CONTINUOUS_ANNOTATION,
1242               Properties.BOOLEANTYPE, "true"));
1243       break;
1244     default:
1245       // don't add any kind of discrete or continous property info.
1246     }
1247   }
1248
1249   /**
1250    * get start<end range of segment, adjusting for inclusivity flag and
1251    * polarity.
1252    * 
1253    * @param visSeg
1254    * @param ensureDirection
1255    *          when true - always ensure start is less than end.
1256    * @return int[] { start, end, direction} where direction==1 for range running
1257    *         from end to start.
1258    */
1259   private int[] getSegRange(Seg visSeg, boolean ensureDirection)
1260   {
1261     boolean incl = visSeg.getInclusive();
1262     // adjust for inclusive flag.
1263     int pol = (visSeg.getStart() <= visSeg.getEnd()) ? 1 : -1; // polarity of
1264     // region.
1265     int start = visSeg.getStart() + (incl ? 0 : pol);
1266     int end = visSeg.getEnd() + (incl ? 0 : -pol);
1267     if (ensureDirection && pol == -1)
1268     {
1269       // jalview doesn't deal with inverted ranges, yet.
1270       int t = end;
1271       end = start;
1272       start = t;
1273     }
1274     return new int[] { start, end, pol < 0 ? 1 : 0 };
1275   }
1276
1277   /**
1278    * 
1279    * @param annotation
1280    * @return true if annotation is not to be stored in document
1281    */
1282   private boolean isJalviewOnly(AlignmentAnnotation annotation)
1283   {
1284     return annotation.autoCalculated || annotation.label.equals("Quality")
1285             || annotation.label.equals("Conservation")
1286             || annotation.label.equals("Consensus");
1287   }
1288
1289   boolean dojvsync = true;
1290
1291   // boolean dojvsync = false; // disables Jalview AppData IO
1292   /**
1293    * list of alignment views created when updating Jalview from document.
1294    */
1295   private final Vector newAlignmentViews = new Vector();
1296
1297   /**
1298    * update local jalview view settings from the stored appdata (if any)
1299    */
1300   public void updateJalviewFromAppdata()
1301   {
1302     // recover any existing Jalview data from appdata
1303     // TODO: recover any PDB files stored as attachments in the vamsas session
1304     // and initialise the Jalview2XML.alreadyLoadedPDB hashtable with mappings
1305     // to temp files.
1306     {
1307       final IClientAppdata cappdata = cdoc.getClientAppdata();
1308       if (cappdata != null)
1309       {
1310         if (cappdata.hasClientAppdata())
1311         {
1312           // TODO: how to check version of Jalview client app data and whether
1313           // it has been modified
1314           // client data is shared over all app clients
1315           try
1316           {
1317             // jalview.gui.Jalview2XML fromxml = new jalview.gui.Jalview2XML();
1318             Jalview2XML fromxml = new Jalview2XML();
1319             fromxml.attemptversion1parse = false;
1320             fromxml.setUniqueSetSuffix("");
1321             fromxml.setObjectMappingTables(vobj2jv, jv2vobj); // mapKeysToString
1322             // and
1323             // mapValuesToString
1324             fromxml.setSkipList(skipList);
1325             jarInputStreamProvider jprovider = new jarInputStreamProvider()
1326             {
1327
1328               @Override
1329               public String getFilename()
1330               {
1331
1332                 // TODO Get the vamsas session ID here
1333                 return "Jalview Vamsas Document Client Data";
1334               }
1335
1336               @Override
1337               public JarInputStream getJarInputStream() throws IOException
1338               {
1339                 jalview.bin.Cache.log.debug(
1340                         "Returning client input stream for Jalview from Vamsas Document.");
1341                 return new JarInputStream(cappdata.getClientInputStream());
1342               }
1343
1344               @Override
1345               public File getFile()
1346               {
1347                 return null;
1348               }
1349             };
1350             if (dojvsync)
1351             {
1352               fromxml.loadJalviewAlign(jprovider);
1353             }
1354           } catch (Exception e)
1355           {
1356
1357           } catch (OutOfMemoryError e)
1358           {
1359
1360           } catch (Error e)
1361           {
1362
1363           }
1364         }
1365       }
1366       if (cappdata.hasUserAppdata())
1367       {
1368         // TODO: how to check version of Jalview user app data and whether it
1369         // has been modified
1370         // user data overrides data shared over all app clients ?
1371         try
1372         {
1373           Jalview2XML fromxml = new Jalview2XML();
1374           fromxml.attemptversion1parse = false;
1375           fromxml.setUniqueSetSuffix("");
1376           fromxml.setSkipList(skipList);
1377           fromxml.setObjectMappingTables(mapKeysToString(vobj2jv),
1378                   mapValuesToString(jv2vobj));
1379           jarInputStreamProvider jarstream = new jarInputStreamProvider()
1380           {
1381
1382             @Override
1383             public String getFilename()
1384             {
1385
1386               // TODO Get the vamsas session ID here
1387               return "Jalview Vamsas Document User Data";
1388             }
1389
1390             @Override
1391             public JarInputStream getJarInputStream() throws IOException
1392             {
1393               jalview.bin.Cache.log.debug(
1394                       "Returning user input stream for Jalview from Vamsas Document.");
1395               return new JarInputStream(cappdata.getUserInputStream());
1396             }
1397
1398             @Override
1399             public File getFile()
1400             {
1401               return null;
1402             }
1403           };
1404           if (dojvsync)
1405           {
1406             fromxml.loadJalviewAlign(jarstream);
1407           }
1408         } catch (Exception e)
1409         {
1410
1411         } catch (OutOfMemoryError e)
1412         {
1413
1414         } catch (Error e)
1415         {
1416
1417         }
1418       }
1419
1420     }
1421     flushAlignViewports();
1422   }
1423
1424   /**
1425    * remove any spurious views generated by document synchronization
1426    */
1427   private void flushAlignViewports()
1428   {
1429     // remove any additional viewports originally recovered from the vamsas
1430     // document.
1431     // search for all alignframes containing viewports generated from document
1432     // sync,
1433     // and if any contain more than one view, then remove the one generated by
1434     // document update.
1435     AlignmentViewport views[], av = null;
1436     AlignFrame af = null;
1437     Iterator newviews = newAlignmentViews.iterator();
1438     while (newviews.hasNext())
1439     {
1440       av = (AlignmentViewport) newviews.next();
1441       af = Desktop.getAlignFrameFor(av);
1442       // TODO implement this : af.getNumberOfViews
1443       String seqsetidobj = av.getSequenceSetId();
1444       views = Desktop.getViewports(seqsetidobj);
1445       Cache.log
1446               .debug("Found " + (views == null ? " no " : "" + views.length)
1447                       + " views for '" + av.getSequenceSetId() + "'");
1448       if (views.length > 1)
1449       {
1450         // we need to close the original document view.
1451
1452         // work out how to do this by seeing if the views are gathered.
1453         // pretty clunky but the only way to do this without adding more flags
1454         // to the align frames.
1455         boolean gathered = false;
1456         String newviewid = null;
1457         List<AlignedCodonFrame> mappings = av.getAlignment()
1458                 .getCodonFrames();
1459         for (int i = 0; i < views.length; i++)
1460         {
1461           if (views[i] != av)
1462           {
1463             AlignFrame viewframe = Desktop.getAlignFrameFor(views[i]);
1464             if (viewframe == af)
1465             {
1466               gathered = true;
1467             }
1468             newviewid = views[i].getSequenceSetId();
1469           }
1470           else
1471           {
1472             // lose the reference to the vamsas document created view
1473             views[i] = null;
1474           }
1475         }
1476         // close the view generated by the vamsas document synchronization
1477         if (gathered)
1478         {
1479           af.closeView(av);
1480         }
1481         else
1482         {
1483           af.closeMenuItem_actionPerformed(false);
1484         }
1485         replaceJvObjMapping(seqsetidobj, newviewid);
1486         seqsetidobj = newviewid;
1487         // not sure if we need to do this:
1488
1489         if (false) // mappings != null)
1490         {
1491           // ensure sequence mappings from vamsas document view still
1492           // active
1493           if (mappings != null)
1494           {
1495             jalview.structure.StructureSelectionManager
1496                     .getStructureSelectionManager(Desktop.getInstance())
1497                     .registerMappings(mappings);
1498           }
1499         }
1500       }
1501       // ensure vamsas object binds to the stored views retrieved from
1502       // Jalview appdata
1503       // jalview.structure.StructureSelectionManager
1504       // .getStructureSelectionManager()
1505       // .addStructureViewerListener(viewframe.alignPanel);
1506
1507     }
1508
1509     newviews = null;
1510     newAlignmentViews.clear();
1511   }
1512
1513   /**
1514    * replaces oldjvobject with newjvobject in the Jalview Object <> VorbaID
1515    * binding tables
1516    * 
1517    * @param oldjvobject
1518    * @param newjvobject
1519    *          (may be null)
1520    */
1521   private void replaceJvObjMapping(Object oldjvobject, Object newjvobject)
1522   {
1523     Object vobject = jv2vobj.remove(oldjvobject);
1524     if (vobject == null)
1525     {
1526       // NOTE: this happens if user deletes object in one session then updates
1527       // from another client
1528       throw new Error(MessageManager.formatMessage(
1529               "error.implementation_error_old_jalview_object_not_bound",
1530               new String[]
1531               { oldjvobject.toString() }));
1532     }
1533     if (newjvobject != null)
1534     {
1535       jv2vobj.put(newjvobject, vobject);
1536       vobj2jv.put(vobject, newjvobject);
1537     }
1538   }
1539
1540   /**
1541    * Update the jalview client and user appdata from the local jalview settings
1542    */
1543   public void updateJalviewClientAppdata()
1544   {
1545     final IClientAppdata cappdata = cdoc.getClientAppdata();
1546     if (cappdata != null)
1547     {
1548       try
1549       {
1550         Jalview2XML jxml = new Jalview2XML();
1551         jxml.setObjectMappingTables(mapKeysToString(vobj2jv),
1552                 mapValuesToString(jv2vobj));
1553         jxml.setSkipList(skipList);
1554         if (dojvsync)
1555         {
1556           jxml.saveState(
1557                   new JarOutputStream(cappdata.getClientOutputStream()));
1558         }
1559
1560       } catch (Exception e)
1561       {
1562         // TODO raise GUI warning if user requests it.
1563         jalview.bin.Cache.log.error(
1564                 "Couldn't update jalview client application data. Giving up - local settings probably lost.",
1565                 e);
1566       }
1567     }
1568     else
1569     {
1570       jalview.bin.Cache.log.error(
1571               "Couldn't access client application data for vamsas session. This is probably a vamsas client bug.");
1572     }
1573   }
1574
1575   /**
1576    * translate the Vobject keys to strings for use in Jalview2XML
1577    * 
1578    * @param jv2vobj2
1579    * @return
1580    */
1581   private IdentityHashMap mapValuesToString(IdentityHashMap jv2vobj2)
1582   {
1583     IdentityHashMap mapped = new IdentityHashMap();
1584     Iterator keys = jv2vobj2.keySet().iterator();
1585     while (keys.hasNext())
1586     {
1587       Object key = keys.next();
1588       mapped.put(key, jv2vobj2.get(key).toString());
1589     }
1590     return mapped;
1591   }
1592
1593   /**
1594    * translate the Vobject values to strings for use in Jalview2XML
1595    * 
1596    * @param vobj2jv2
1597    * @return hashtable with string values
1598    */
1599   private Hashtable mapKeysToString(Hashtable vobj2jv2)
1600   {
1601     Hashtable mapped = new Hashtable();
1602     Iterator keys = vobj2jv2.keySet().iterator();
1603     while (keys.hasNext())
1604     {
1605       Object key = keys.next();
1606       mapped.put(key.toString(), vobj2jv2.get(key));
1607     }
1608     return mapped;
1609   }
1610
1611   /**
1612    * synchronize Jalview from the vamsas document
1613    * 
1614    * @return number of new views from document
1615    */
1616   public int updateToJalview()
1617   {
1618     VAMSAS _roots[] = cdoc.getVamsasRoots();
1619
1620     for (int _root = 0; _root < _roots.length; _root++)
1621     {
1622       VAMSAS root = _roots[_root];
1623       boolean newds = false;
1624       for (int _ds = 0, _nds = root.getDataSetCount(); _ds < _nds; _ds++)
1625       {
1626         // ///////////////////////////////////
1627         // ///LOAD DATASET
1628         DataSet dataset = root.getDataSet(_ds);
1629         int i, iSize = dataset.getSequenceCount();
1630         List<SequenceI> dsseqs;
1631         jalview.datamodel.Alignment jdataset = (jalview.datamodel.Alignment) getvObj2jv(
1632                 dataset);
1633         int jremain = 0;
1634         if (jdataset == null)
1635         {
1636           Cache.log.debug("Initialising new jalview dataset fields");
1637           newds = true;
1638           dsseqs = new Vector();
1639         }
1640         else
1641         {
1642           Cache.log.debug("Update jalview dataset from vamsas.");
1643           jremain = jdataset.getHeight();
1644           dsseqs = jdataset.getSequences();
1645         }
1646
1647         // TODO: test sequence merging - we preserve existing non vamsas
1648         // sequences but add in any new vamsas ones, and don't yet update any
1649         // sequence attributes
1650         for (i = 0; i < iSize; i++)
1651         {
1652           Sequence vdseq = dataset.getSequence(i);
1653           jalview.io.vamsas.Datasetsequence dssync = new Datasetsequence(
1654                   this, vdseq);
1655
1656           jalview.datamodel.SequenceI dsseq = (SequenceI) dssync.getJvobj();
1657           if (dssync.isAddfromdoc())
1658           {
1659             dsseqs.add(dsseq);
1660           }
1661           if (vdseq.getDbRefCount() > 0)
1662           {
1663             DbRef[] dbref = vdseq.getDbRef();
1664             for (int db = 0; db < dbref.length; db++)
1665             {
1666               new jalview.io.vamsas.Dbref(this, dbref[db], vdseq, dsseq);
1667
1668             }
1669             dsseq.updatePDBIds();
1670           }
1671         }
1672
1673         if (newds)
1674         {
1675           SequenceI[] seqs = new SequenceI[dsseqs.size()];
1676           for (i = 0, iSize = dsseqs.size(); i < iSize; i++)
1677           {
1678             seqs[i] = dsseqs.get(i);
1679             dsseqs.set(i, null);
1680           }
1681           jdataset = new jalview.datamodel.Alignment(seqs);
1682           Cache.log.debug("New vamsas dataset imported into jalview.");
1683           bindjvvobj(jdataset, dataset);
1684         }
1685         // ////////
1686         // add any new dataset sequence feature annotations
1687         if (dataset.getDataSetAnnotations() != null)
1688         {
1689           for (int dsa = 0; dsa < dataset
1690                   .getDataSetAnnotationsCount(); dsa++)
1691           {
1692             DataSetAnnotations dseta = dataset.getDataSetAnnotations(dsa);
1693             // TODO: deal with group annotation on datset sequences.
1694             if (dseta.getSeqRefCount() == 1)
1695             {
1696               SequenceI dsSeq = (SequenceI) getvObj2jv(
1697                       (Vobject) dseta.getSeqRef(0)); // TODO: deal with group
1698                                                      // dataset
1699               // annotations
1700               if (dsSeq == null)
1701               {
1702                 jalview.bin.Cache.log.warn(
1703                         "Couldn't resolve jalview sequenceI for dataset object reference "
1704                                 + ((Vobject) dataset
1705                                         .getDataSetAnnotations(dsa)
1706                                         .getSeqRef(0)).getVorbaId()
1707                                                 .getId());
1708               }
1709               else
1710               {
1711                 if (dseta.getAnnotationElementCount() == 0)
1712                 {
1713                   new jalview.io.vamsas.Sequencefeature(this, dseta, dsSeq);
1714
1715                 }
1716                 else
1717                 {
1718                   // TODO: deal with alignmentAnnotation style annotation
1719                   // appearing on dataset sequences.
1720                   // JBPNote: we could just add them to all alignments but
1721                   // that may complicate cross references in the jalview
1722                   // datamodel
1723                   Cache.log.warn(
1724                           "Ignoring dataset annotation with annotationElements. Not yet supported in jalview.");
1725                 }
1726               }
1727             }
1728             else
1729             {
1730               Cache.log.warn(
1731                       "Ignoring multiply referenced dataset sequence annotation for binding to datsaet sequence features.");
1732             }
1733           }
1734         }
1735         if (dataset.getAlignmentCount() > 0)
1736         {
1737           // LOAD ALIGNMENTS from DATASET
1738
1739           for (int al = 0, nal = dataset
1740                   .getAlignmentCount(); al < nal; al++)
1741           {
1742             uk.ac.vamsas.objects.core.Alignment alignment = dataset
1743                     .getAlignment(al);
1744             // TODO check this handles multiple views properly
1745             AlignmentViewport av = findViewport(alignment);
1746
1747             jalview.datamodel.AlignmentI jal = null;
1748             if (av != null)
1749             {
1750               // TODO check that correct alignment object is retrieved when
1751               // hidden seqs exist.
1752               jal = (av.hasHiddenRows()) ? av.getAlignment()
1753                       .getHiddenSequences().getFullAlignment()
1754                       : av.getAlignment();
1755             }
1756             iSize = alignment.getAlignmentSequenceCount();
1757             boolean refreshal = false;
1758             Vector newasAnnots = new Vector();
1759             char gapChar = ' '; // default for new alignments read in from the
1760             // document
1761             if (jal != null)
1762             {
1763               dsseqs = jal.getSequences(); // for merge/update
1764               gapChar = jal.getGapCharacter();
1765             }
1766             else
1767             {
1768               dsseqs = new Vector();
1769             }
1770             char valGapchar = alignment.getGapChar().charAt(0);
1771             for (i = 0; i < iSize; i++)
1772             {
1773               AlignmentSequence valseq = alignment.getAlignmentSequence(i);
1774               jalview.datamodel.Sequence alseq = (jalview.datamodel.Sequence) getvObj2jv(
1775                       valseq);
1776               if (syncFromAlignmentSequence(valseq, valGapchar, gapChar,
1777                       dsseqs) && alseq != null)
1778               {
1779
1780                 // updated to sequence from the document
1781                 jremain--;
1782                 refreshal = true;
1783               }
1784               if (valseq.getAlignmentSequenceAnnotationCount() > 0)
1785               {
1786                 AlignmentSequenceAnnotation[] vasannot = valseq
1787                         .getAlignmentSequenceAnnotation();
1788                 for (int a = 0; a < vasannot.length; a++)
1789                 {
1790                   jalview.datamodel.AlignmentAnnotation asa = (jalview.datamodel.AlignmentAnnotation) getvObj2jv(
1791                           vasannot[a]); // TODO:
1792                   // 1:many
1793                   // jalview
1794                   // alignment
1795                   // sequence
1796                   // annotations
1797                   if (asa == null)
1798                   {
1799                     int se[] = getBounds(vasannot[a]);
1800                     asa = getjAlignmentAnnotation(jal, vasannot[a]);
1801                     asa.setSequenceRef(alseq);
1802                     asa.createSequenceMapping(alseq, se[0], false); // TODO:
1803                     // verify
1804                     // that
1805                     // positions
1806                     // in
1807                     // alseqAnnotation
1808                     // correspond
1809                     // to
1810                     // ungapped
1811                     // residue
1812                     // positions.
1813                     alseq.addAlignmentAnnotation(asa);
1814                     bindjvvobj(asa, vasannot[a]);
1815                     refreshal = true;
1816                     newasAnnots.add(asa);
1817                   }
1818                   else
1819                   {
1820                     // update existing annotation - can do this in place
1821                     if (vasannot[a].getModifiable() == null) // TODO: USE
1822                     // VAMSAS LIBRARY
1823                     // OBJECT LOCK
1824                     // METHODS)
1825                     {
1826                       Cache.log.info(
1827                               "UNIMPLEMENTED: not recovering user modifiable sequence alignment annotation");
1828                       // TODO: should at least replace with new one - otherwise
1829                       // things will break
1830                       // basically do this:
1831                       // int se[] = getBounds(vasannot[a]);
1832                       // asa.update(getjAlignmentAnnotation(jal, vasannot[a]));
1833                       // // update from another annotation object in place.
1834                       // asa.createSequenceMapping(alseq, se[0], false);
1835
1836                     }
1837                   }
1838                 }
1839               }
1840             }
1841             if (jal == null)
1842             {
1843               SequenceI[] seqs = new SequenceI[dsseqs.size()];
1844               for (i = 0, iSize = dsseqs.size(); i < iSize; i++)
1845               {
1846                 seqs[i] = dsseqs.get(i);
1847                 dsseqs.set(i, null);
1848               }
1849               jal = new jalview.datamodel.Alignment(seqs);
1850               Cache.log.debug("New vamsas alignment imported into jalview "
1851                       + alignment.getVorbaId().getId());
1852               jal.setDataset(jdataset);
1853             }
1854             if (newasAnnots != null && newasAnnots.size() > 0)
1855             {
1856               // Add the new sequence annotations in to the alignment.
1857               for (int an = 0, anSize = newasAnnots
1858                       .size(); an < anSize; an++)
1859               {
1860                 jal.addAnnotation(
1861                         (AlignmentAnnotation) newasAnnots.elementAt(an));
1862                 // TODO: check if anything has to be done - like calling
1863                 // adjustForAlignment or something.
1864                 newasAnnots.setElementAt(null, an);
1865               }
1866               newasAnnots = null;
1867             }
1868             // //////////////////////////////////////////
1869             // //LOAD ANNOTATIONS FOR THE ALIGNMENT
1870             // ////////////////////////////////////
1871             if (alignment.getAlignmentAnnotationCount() > 0)
1872             {
1873               uk.ac.vamsas.objects.core.AlignmentAnnotation[] an = alignment
1874                       .getAlignmentAnnotation();
1875
1876               for (int j = 0; j < an.length; j++)
1877               {
1878                 jalview.datamodel.AlignmentAnnotation jan = (jalview.datamodel.AlignmentAnnotation) getvObj2jv(
1879                         an[j]);
1880                 if (jan != null)
1881                 {
1882                   // update or stay the same.
1883                   // TODO: should at least replace with a new one - otherwise
1884                   // things will break
1885                   // basically do this:
1886                   // jan.update(getjAlignmentAnnotation(jal, an[a])); // update
1887                   // from another annotation object in place.
1888
1889                   Cache.log.debug(
1890                           "update from vamsas alignment annotation to existing jalview alignment annotation.");
1891                   if (an[j].getModifiable() == null) // TODO: USE VAMSAS
1892                   // LIBRARY OBJECT LOCK
1893                   // METHODS)
1894                   {
1895                     // TODO: user defined annotation is totally mutable... - so
1896                     // load it up or throw away if locally edited.
1897                     Cache.log.info(
1898                             "NOT IMPLEMENTED - Recovering user-modifiable annotation - yet...");
1899                   }
1900                   // TODO: compare annotation element rows
1901                   // TODO: compare props.
1902                 }
1903                 else
1904                 {
1905                   jan = getjAlignmentAnnotation(jal, an[j]);
1906                   // TODO: ensure we add the alignment annotation before the
1907                   // automatic annotation rows
1908                   jal.addAnnotation(jan);
1909                   bindjvvobj(jan, an[j]);
1910                   refreshal = true;
1911                 }
1912               }
1913             }
1914             AlignFrame alignFrame;
1915             if (av == null)
1916             {
1917               Cache.log.debug("New alignframe for alignment "
1918                       + alignment.getVorbaId());
1919               // ///////////////////////////////
1920               // construct alignment view
1921               alignFrame = new AlignFrame(jal, AlignFrame.DEFAULT_WIDTH,
1922                       AlignFrame.DEFAULT_HEIGHT,
1923                       alignment.getVorbaId().toString());
1924               av = alignFrame.getViewport();
1925               newAlignmentViews.addElement(av);
1926               String title = alignment.getProvenance()
1927                       .getEntry(
1928                               alignment.getProvenance().getEntryCount() - 1)
1929                       .getAction();
1930               if (alignment.getPropertyCount() > 0)
1931               {
1932                 for (int p = 0, pe = alignment
1933                         .getPropertyCount(); p < pe; p++)
1934                 {
1935                   if (alignment.getProperty(p).getName().equals("title"))
1936                   {
1937                     title = alignment.getProperty(p).getContent();
1938                   }
1939                 }
1940               }
1941               // TODO: automatically create meaningful title for a vamsas
1942               // alignment using its provenance.
1943               if (Cache.log.isDebugEnabled())
1944               {
1945                 title = title + "(" + alignment.getVorbaId() + ")";
1946
1947               }
1948               jalview.gui.Desktop.addInternalFrame(alignFrame, title,
1949                       AlignFrame.DEFAULT_WIDTH, AlignFrame.DEFAULT_HEIGHT);
1950               bindjvvobj(av.getSequenceSetId(), alignment);
1951             }
1952             else
1953             {
1954               // find the alignFrame for jal.
1955               // TODO: fix this so we retrieve the alignFrame handing av
1956               // *directly* (JBPNote - don't understand this now)
1957               // TODO: make sure all associated views are refreshed
1958               alignFrame = Desktop.getAlignFrameFor(av);
1959               if (refreshal)
1960               {
1961                 av.alignmentChanged(alignFrame.alignPanel);
1962                 alignFrame.alignPanel.adjustAnnotationHeight();
1963               }
1964             }
1965             // LOAD TREES
1966             // /////////////////////////////////////
1967             if (alignment.getTreeCount() > 0)
1968             {
1969
1970               for (int t = 0; t < alignment.getTreeCount(); t++)
1971               {
1972                 jalview.io.vamsas.Tree vstree = new jalview.io.vamsas.Tree(
1973                         this, alignFrame, alignment.getTree(t));
1974                 TreePanel tp = null;
1975                 if (vstree.isValidTree())
1976                 {
1977                   tp = alignFrame.showNewickTree(vstree.getNewickTree(),
1978                           vstree.getTitle(), vstree.getInputData(), 600,
1979                           500, t * 20 + 50, t * 20 + 50);
1980
1981                 }
1982                 if (tp != null)
1983                 {
1984                   bindjvvobj(tp, alignment.getTree(t));
1985                   try
1986                   {
1987                     vstree.UpdateSequenceTreeMap(tp);
1988                   } catch (RuntimeException e)
1989                   {
1990                     Cache.log.warn("update of labels failed.", e);
1991                   }
1992                 }
1993                 else
1994                 {
1995                   Cache.log.warn("Cannot create tree for tree " + t
1996                           + " in document ("
1997                           + alignment.getTree(t).getVorbaId());
1998                 }
1999
2000               }
2001             }
2002           }
2003         }
2004       }
2005       // we do sequenceMappings last because they span all datasets in a vamsas
2006       // root
2007       for (int _ds = 0, _nds = root.getDataSetCount(); _ds < _nds; _ds++)
2008       {
2009         DataSet dataset = root.getDataSet(_ds);
2010         if (dataset.getSequenceMappingCount() > 0)
2011         {
2012           for (int sm = 0, smCount = dataset
2013                   .getSequenceMappingCount(); sm < smCount; sm++)
2014           {
2015             Rangetype seqmap = new jalview.io.vamsas.Sequencemapping(this,
2016                     dataset.getSequenceMapping(sm));
2017           }
2018         }
2019       }
2020     }
2021     return newAlignmentViews.size();
2022   }
2023
2024   public AlignmentViewport findViewport(Alignment alignment)
2025   {
2026     AlignmentViewport av = null;
2027     AlignmentViewport[] avs = Desktop
2028             .getViewports((String) getvObj2jv(alignment));
2029     if (avs != null)
2030     {
2031       av = avs[0];
2032     }
2033     return av;
2034   }
2035
2036   // bitfields - should be a template in j1.5
2037   private static int HASSECSTR = 0;
2038
2039   private static int HASVALS = 1;
2040
2041   private static int HASHPHOB = 2;
2042
2043   private static int HASDC = 3;
2044
2045   private static int HASDESCSTR = 4;
2046
2047   private static int HASTWOSTATE = 5; // not used yet.
2048
2049   /**
2050    * parses the AnnotationElements - if they exist - into
2051    * jalview.datamodel.Annotation[] rows Two annotation rows are made if there
2052    * are distinct annotation for both at 'pos' and 'after pos' at any particular
2053    * site.
2054    * 
2055    * @param annotation
2056    * @return { boolean[static int constants ], int[ae.length] - map to annotated
2057    *         object frame, jalview.datamodel.Annotation[],
2058    *         jalview.datamodel.Annotation[] (after)}
2059    */
2060   private Object[] parseRangeAnnotation(
2061           uk.ac.vamsas.objects.core.RangeAnnotation annotation)
2062   {
2063     // set these attributes by looking in the annotation to decide what kind of
2064     // alignment annotation rows will be made
2065     // TODO: potentially we might make several annotation rows from one vamsas
2066     // alignment annotation. the jv2Vobj binding mechanism
2067     // may not quite cope with this (without binding an array of annotations to
2068     // a vamsas alignment annotation)
2069     // summary flags saying what we found over the set of annotation rows.
2070     boolean[] AeContent = new boolean[] { false, false, false, false,
2071         false };
2072     int[] rangeMap = getMapping(annotation);
2073     jalview.datamodel.Annotation[][] anot = new jalview.datamodel.Annotation[][] {
2074         new jalview.datamodel.Annotation[rangeMap.length],
2075         new jalview.datamodel.Annotation[rangeMap.length] };
2076     boolean mergeable = true; // false if 'after positions cant be placed on
2077     // same annotation row as positions.
2078
2079     if (annotation.getAnnotationElementCount() > 0)
2080     {
2081       AnnotationElement ae[] = annotation.getAnnotationElement();
2082       for (int aa = 0; aa < ae.length; aa++)
2083       {
2084         int pos = (int) ae[aa].getPosition() - 1; // pos counts from 1 to
2085         // (|seg.start-seg.end|+1)
2086         if (pos >= 0 && pos < rangeMap.length)
2087         {
2088           int row = ae[aa].getAfter() ? 1 : 0;
2089           if (anot[row][pos] != null)
2090           {
2091             // only time this should happen is if the After flag is set.
2092             Cache.log.debug("Ignoring duplicate annotation site at " + pos);
2093             continue;
2094           }
2095           if (anot[1 - row][pos] != null)
2096           {
2097             mergeable = false;
2098           }
2099           String desc = "";
2100           if (ae[aa].getDescription() != null)
2101           {
2102             desc = ae[aa].getDescription();
2103             if (desc.length() > 0)
2104             {
2105               // have imported valid description string
2106               AeContent[HASDESCSTR] = true;
2107             }
2108           }
2109           String dc = null; // ae[aa].getDisplayCharacter()==null ? "dc" :
2110           // ae[aa].getDisplayCharacter();
2111           String ss = null; // ae[aa].getSecondaryStructure()==null ? "ss" :
2112           // ae[aa].getSecondaryStructure();
2113           java.awt.Color colour = null;
2114           if (ae[aa].getGlyphCount() > 0)
2115           {
2116             Glyph[] glyphs = ae[aa].getGlyph();
2117             for (int g = 0; g < glyphs.length; g++)
2118             {
2119               if (glyphs[g].getDict().equals(
2120                       uk.ac.vamsas.objects.utils.GlyphDictionary.PROTEIN_SS_3STATE))
2121               {
2122                 ss = glyphs[g].getContent();
2123                 AeContent[HASSECSTR] = true;
2124               }
2125               else if (glyphs[g].getDict().equals(
2126                       uk.ac.vamsas.objects.utils.GlyphDictionary.PROTEIN_HD_HYDRO))
2127               {
2128                 Cache.log.debug("ignoring hydrophobicity glyph marker.");
2129                 AeContent[HASHPHOB] = true;
2130                 char c = (dc = glyphs[g].getContent()).charAt(0);
2131                 // dc may get overwritten - but we still set the colour.
2132                 colour = new java.awt.Color(c == '+' ? 255 : 0,
2133                         c == '.' ? 255 : 0, c == '-' ? 255 : 0);
2134
2135               }
2136               else if (glyphs[g].getDict().equals(
2137                       uk.ac.vamsas.objects.utils.GlyphDictionary.DEFAULT))
2138               {
2139                 dc = glyphs[g].getContent();
2140                 AeContent[HASDC] = true;
2141               }
2142               else
2143               {
2144                 Cache.log.debug(
2145                         "IMPLEMENTATION TODO: Ignoring unknown glyph type "
2146                                 + glyphs[g].getDict());
2147               }
2148             }
2149           }
2150           float val = 0;
2151           if (ae[aa].getValueCount() > 0)
2152           {
2153             AeContent[HASVALS] = true;
2154             if (ae[aa].getValueCount() > 1)
2155             {
2156               Cache.log.warn(
2157                       "ignoring additional " + (ae[aa].getValueCount() - 1)
2158                               + " values in annotation element.");
2159             }
2160             val = ae[aa].getValue(0);
2161           }
2162           if (colour == null)
2163           {
2164             anot[row][pos] = new jalview.datamodel.Annotation(
2165                     (dc != null) ? dc : "", desc,
2166                     (ss != null) ? ss.charAt(0) : ' ', val);
2167           }
2168           else
2169           {
2170             anot[row][pos] = new jalview.datamodel.Annotation(
2171                     (dc != null) ? dc : "", desc,
2172                     (ss != null) ? ss.charAt(0) : ' ', val, colour);
2173           }
2174         }
2175         else
2176         {
2177           Cache.log.warn("Ignoring out of bound annotation element " + aa
2178                   + " in " + annotation.getVorbaId().getId());
2179         }
2180       }
2181       // decide on how many annotation rows are needed.
2182       if (mergeable)
2183       {
2184         for (int i = 0; i < anot[0].length; i++)
2185         {
2186           if (anot[1][i] != null)
2187           {
2188             anot[0][i] = anot[1][i];
2189             anot[0][i].description = anot[0][i].description + " (after)";
2190             AeContent[HASDESCSTR] = true; // we have valid description string
2191             // data
2192             anot[1][i] = null;
2193           }
2194         }
2195         anot[1] = null;
2196       }
2197       else
2198       {
2199         for (int i = 0; i < anot[0].length; i++)
2200         {
2201           anot[1][i].description = anot[1][i].description + " (after)";
2202         }
2203       }
2204       return new Object[] { AeContent, rangeMap, anot[0], anot[1] };
2205     }
2206     else
2207     {
2208       // no annotations to parse. Just return an empty annotationElement[]
2209       // array.
2210       return new Object[] { AeContent, rangeMap, anot[0], anot[1] };
2211     }
2212     // return null;
2213   }
2214
2215   /**
2216    * @param jal
2217    *          the jalview alignment to which the annotation will be attached
2218    *          (ideally - freshly updated from corresponding vamsas alignment)
2219    * @param annotation
2220    * @return unbound jalview alignment annotation object.
2221    */
2222   private jalview.datamodel.AlignmentAnnotation getjAlignmentAnnotation(
2223           jalview.datamodel.AlignmentI jal,
2224           uk.ac.vamsas.objects.core.RangeAnnotation annotation)
2225   {
2226     if (annotation == null)
2227     {
2228       return null;
2229     }
2230     // boolean
2231     // hasSequenceRef=annotation.getClass().equals(uk.ac.vamsas.objects.core.AlignmentSequenceAnnotation.class);
2232     // boolean hasProvenance=hasSequenceRef ||
2233     // (annotation.getClass().equals(uk.ac.vamsas.objects.core.AlignmentAnnotation.class));
2234     /*
2235      * int se[] = getBounds(annotation); if (se==null) se=new int[]
2236      * {0,jal.getWidth()-1};
2237      */
2238     Object[] parsedRangeAnnotation = parseRangeAnnotation(annotation);
2239     String a_label = annotation.getLabel();
2240     String a_descr = annotation.getDescription();
2241     GraphLine gl = null;
2242     int type = 0;
2243     boolean interp = true; // cleared if annotation is DISCRETE
2244     // set type and other attributes from properties
2245     if (annotation.getPropertyCount() > 0)
2246     {
2247       // look for special jalview properties
2248       uk.ac.vamsas.objects.core.Property[] props = annotation.getProperty();
2249       for (int p = 0; p < props.length; p++)
2250       {
2251         if (props[p].getName().equalsIgnoreCase(DISCRETE_ANNOTATION))
2252         {
2253           type = AlignmentAnnotation.BAR_GRAPH;
2254           interp = false;
2255         }
2256         else if (props[p].getName().equalsIgnoreCase(CONTINUOUS_ANNOTATION))
2257         {
2258           type = AlignmentAnnotation.LINE_GRAPH;
2259         }
2260         else if (props[p].getName().equalsIgnoreCase(THRESHOLD))
2261         {
2262           Float val = null;
2263           try
2264           {
2265             val = Float.valueOf(props[p].getContent());
2266           } catch (Exception e)
2267           {
2268             Cache.log.warn("Failed to parse threshold property");
2269           }
2270           if (val != null)
2271           {
2272             if (gl == null)
2273             {
2274               gl = new GraphLine(val.floatValue(), "",
2275                       java.awt.Color.black);
2276             }
2277             else
2278             {
2279               gl.value = val.floatValue();
2280             }
2281           }
2282         }
2283         else if (props[p].getName().equalsIgnoreCase(THRESHOLD + "Name"))
2284         {
2285           if (gl == null)
2286           {
2287             gl = new GraphLine(0, "", java.awt.Color.black);
2288           }
2289           gl.label = props[p].getContent();
2290         }
2291       }
2292     }
2293     jalview.datamodel.AlignmentAnnotation jan = null;
2294     if (a_label == null || a_label.length() == 0)
2295     {
2296       a_label = annotation.getType();
2297       if (a_label.length() == 0)
2298       {
2299         a_label = "Unamed annotation";
2300       }
2301     }
2302     if (a_descr == null || a_descr.length() == 0)
2303     {
2304       a_descr = "Annotation of type '" + annotation.getType() + "'";
2305     }
2306     if (parsedRangeAnnotation == null)
2307     {
2308       Cache.log.debug(
2309               "Inserting empty annotation row elements for a whole-alignment annotation.");
2310     }
2311     else
2312     {
2313       if (parsedRangeAnnotation[3] != null)
2314       {
2315         Cache.log.warn("Ignoring 'After' annotation row in "
2316                 + annotation.getVorbaId());
2317       }
2318       jalview.datamodel.Annotation[] arow = (jalview.datamodel.Annotation[]) parsedRangeAnnotation[2];
2319       boolean[] has = (boolean[]) parsedRangeAnnotation[0];
2320       // VAMSAS: getGraph is only on derived annotation for alignments - in this
2321       // way its 'odd' - there is already an existing TODO about removing this
2322       // flag as being redundant
2323       /*
2324        * if((annotation.getClass().equals(uk.ac.vamsas.objects.core.
2325        * AlignmentAnnotation.class) &&
2326        * ((uk.ac.vamsas.objects.core.AlignmentAnnotation)annotation).getGraph())
2327        * || (hasSequenceRef=true &&
2328        * ((uk.ac.vamsas.objects.core.AlignmentSequenceAnnotation
2329        * )annotation).getGraph())) {
2330        */
2331       if (has[HASVALS])
2332       {
2333         if (type == 0)
2334         {
2335           type = jalview.datamodel.AlignmentAnnotation.BAR_GRAPH; // default
2336           // type of
2337           // value
2338           // annotation
2339           if (has[HASHPHOB])
2340           {
2341             // no hints - so we ensure HPHOB display is like this.
2342             type = jalview.datamodel.AlignmentAnnotation.BAR_GRAPH;
2343           }
2344         }
2345         // make bounds and automatic description strings for jalview user's
2346         // benefit (these shouldn't be written back to vamsas document)
2347         boolean first = true;
2348         float min = 0, max = 1;
2349         int lastval = 0;
2350         for (int i = 0; i < arow.length; i++)
2351         {
2352           if (arow[i] != null)
2353           {
2354             if (i - lastval > 1 && interp)
2355             {
2356               // do some interpolation *between* points
2357               if (arow[lastval] != null)
2358               {
2359                 float interval = arow[i].value - arow[lastval].value;
2360                 interval /= i - lastval;
2361                 float base = arow[lastval].value;
2362                 for (int ip = lastval + 1, np = 0; ip < i; np++, ip++)
2363                 {
2364                   arow[ip] = new jalview.datamodel.Annotation("", "", ' ',
2365                           interval * np + base);
2366                   // NB - Interpolated points don't get a tooltip and
2367                   // description.
2368                 }
2369               }
2370             }
2371             lastval = i;
2372             // check range - shouldn't we have a min and max property in the
2373             // annotation object ?
2374             if (first)
2375             {
2376               min = max = arow[i].value;
2377               first = false;
2378             }
2379             else
2380             {
2381               if (arow[i].value < min)
2382               {
2383                 min = arow[i].value;
2384               }
2385               else if (arow[i].value > max)
2386               {
2387                 max = arow[i].value;
2388               }
2389             }
2390             // make tooltip and display char value
2391             if (!has[HASDESCSTR])
2392             {
2393               arow[i].description = arow[i].value + "";
2394             }
2395             if (!has[HASDC])
2396             {
2397               if (!interp)
2398               {
2399                 if (arow[i].description != null
2400                         && arow[i].description.length() < 3)
2401                 {
2402                   // copy over the description as the display char.
2403                   arow[i].displayCharacter = new String(
2404                           arow[i].description);
2405                 }
2406               }
2407               else
2408               {
2409                 // mark the position as a point used for the interpolation.
2410                 arow[i].displayCharacter = arow[i].value + "";
2411               }
2412             }
2413           }
2414         }
2415         jan = new jalview.datamodel.AlignmentAnnotation(a_label, a_descr,
2416                 arow, min, max, type);
2417       }
2418       else
2419       {
2420         if (annotation.getAnnotationElementCount() == 0)
2421         {
2422           // empty annotation array
2423           // TODO: alignment 'features' compare rangeType spec to alignment
2424           // width - if it is not complete, then mark regions on the annotation
2425           // row.
2426         }
2427         jan = new jalview.datamodel.AlignmentAnnotation(a_label, a_descr,
2428                 arow);
2429         jan.setThreshold(null);
2430         jan.annotationId = annotation.getVorbaId().toString(); // keep all the
2431         // ids together.
2432       }
2433       if (annotation.getLinkCount() > 0)
2434       {
2435         Cache.log.warn("Ignoring " + annotation.getLinkCount()
2436                 + "links added to AlignmentAnnotation.");
2437       }
2438       if (annotation.getModifiable() == null
2439               || annotation.getModifiable().length() == 0) // TODO: USE VAMSAS
2440       // LIBRARY OBJECT
2441       // LOCK METHODS)
2442       {
2443         jan.editable = true;
2444       }
2445       try
2446       {
2447         if (annotation.getGroup() != null
2448                 && annotation.getGroup().length() > 0)
2449         {
2450           jan.graphGroup = Integer.parseInt(annotation.getGroup()); // TODO:
2451           // group
2452           // similarly
2453           // named
2454           // annotation
2455           // together
2456           // ?
2457         }
2458       } catch (Exception e)
2459       {
2460         Cache.log.info(
2461                 "UNIMPLEMENTED : Couldn't parse non-integer group value for setting graphGroup correctly.");
2462       }
2463       return jan;
2464
2465     }
2466
2467     return null;
2468   }
2469
2470   /**
2471    * get real bounds of a RangeType's specification. start and end are an
2472    * inclusive range within which all segments and positions lie. TODO: refactor
2473    * to vamsas utils
2474    * 
2475    * @param dseta
2476    * @return int[] { start, end}
2477    */
2478   private int[] getBounds(RangeType dseta)
2479   {
2480     if (dseta != null)
2481     {
2482       int[] se = null;
2483       if (dseta.getSegCount() > 0 && dseta.getPosCount() > 0)
2484       {
2485         throw new Error(MessageManager.getString(
2486                 "error.invalid_vamsas_rangetype_cannot_resolve_lists"));
2487       }
2488       if (dseta.getSegCount() > 0)
2489       {
2490         se = getSegRange(dseta.getSeg(0), true);
2491         for (int s = 1, sSize = dseta.getSegCount(); s < sSize; s++)
2492         {
2493           int nse[] = getSegRange(dseta.getSeg(s), true);
2494           if (se[0] > nse[0])
2495           {
2496             se[0] = nse[0];
2497           }
2498           if (se[1] < nse[1])
2499           {
2500             se[1] = nse[1];
2501           }
2502         }
2503       }
2504       if (dseta.getPosCount() > 0)
2505       {
2506         // could do a polarity for pos range too. and pass back indication of
2507         // discontinuities.
2508         int pos = dseta.getPos(0).getI();
2509         se = new int[] { pos, pos };
2510         for (int p = 0, pSize = dseta.getPosCount(); p < pSize; p++)
2511         {
2512           pos = dseta.getPos(p).getI();
2513           if (se[0] > pos)
2514           {
2515             se[0] = pos;
2516           }
2517           if (se[1] < pos)
2518           {
2519             se[1] = pos;
2520           }
2521         }
2522       }
2523       return se;
2524     }
2525     return null;
2526   }
2527
2528   /**
2529    * map from a rangeType's internal frame to the referenced object's coordinate
2530    * frame.
2531    * 
2532    * @param dseta
2533    * @return int [] { ref(pos)...} for all pos in rangeType's frame.
2534    */
2535   private int[] getMapping(RangeType dseta)
2536   {
2537     Vector posList = new Vector();
2538     if (dseta != null)
2539     {
2540       int[] se = null;
2541       if (dseta.getSegCount() > 0 && dseta.getPosCount() > 0)
2542       {
2543         throw new Error(MessageManager.getString(
2544                 "error.invalid_vamsas_rangetype_cannot_resolve_lists"));
2545       }
2546       if (dseta.getSegCount() > 0)
2547       {
2548         for (int s = 0, sSize = dseta.getSegCount(); s < sSize; s++)
2549         {
2550           se = getSegRange(dseta.getSeg(s), false);
2551           int se_end = se[1 - se[2]] + (se[2] == 0 ? 1 : -1);
2552           for (int p = se[se[2]]; p != se_end; p += se[2] == 0 ? 1 : -1)
2553           {
2554             posList.add(Integer.valueOf(p));
2555           }
2556         }
2557       }
2558       else if (dseta.getPosCount() > 0)
2559       {
2560         int pos = dseta.getPos(0).getI();
2561
2562         for (int p = 0, pSize = dseta.getPosCount(); p < pSize; p++)
2563         {
2564           pos = dseta.getPos(p).getI();
2565           posList.add(Integer.valueOf(pos));
2566         }
2567       }
2568     }
2569     if (posList != null && posList.size() > 0)
2570     {
2571       int[] range = new int[posList.size()];
2572       for (int i = 0; i < range.length; i++)
2573       {
2574         range[i] = ((Integer) posList.elementAt(i)).intValue();
2575       }
2576       posList.clear();
2577       return range;
2578     }
2579     return null;
2580   }
2581
2582   /**
2583    * 
2584    * @param maprange
2585    *          where the from range is the local mapped range, and the to range
2586    *          is the 'mapped' range in the MapRangeType
2587    * @param default
2588    *          unit for local
2589    * @param default
2590    *          unit for mapped
2591    * @return MapList
2592    */
2593   private jalview.util.MapList parsemapType(MapType maprange, int localu,
2594           int mappedu)
2595   {
2596     jalview.util.MapList ml = null;
2597     int[] localRange = getMapping(maprange.getLocal());
2598     int[] mappedRange = getMapping(maprange.getMapped());
2599     long lu = maprange.getLocal().hasUnit() ? maprange.getLocal().getUnit()
2600             : localu;
2601     long mu = maprange.getMapped().hasUnit()
2602             ? maprange.getMapped().getUnit()
2603             : mappedu;
2604     ml = new jalview.util.MapList(localRange, mappedRange, (int) lu,
2605             (int) mu);
2606     return ml;
2607   }
2608
2609   /**
2610    * initialise a range type object from a set of start/end inclusive intervals
2611    * 
2612    * @param mrt
2613    * @param ranges
2614    */
2615   private void initRangeType(RangeType mrt, List<int[]> ranges)
2616   {
2617     for (int[] range : ranges)
2618     {
2619       Seg vSeg = new Seg();
2620       vSeg.setStart(range[0]);
2621       vSeg.setEnd(range[1]);
2622       mrt.addSeg(vSeg);
2623     }
2624   }
2625
2626   /**
2627    * initialise a MapType object from a MapList object.
2628    * 
2629    * @param maprange
2630    * @param ml
2631    * @param setUnits
2632    */
2633   private void initMapType(MapType maprange, jalview.util.MapList ml,
2634           boolean setUnits)
2635   {
2636     maprange.setLocal(new Local());
2637     maprange.setMapped(new Mapped());
2638     initRangeType(maprange.getLocal(), ml.getFromRanges());
2639     initRangeType(maprange.getMapped(), ml.getToRanges());
2640     if (setUnits)
2641     {
2642       maprange.getLocal().setUnit(ml.getFromRatio());
2643       maprange.getLocal().setUnit(ml.getToRatio());
2644     }
2645   }
2646
2647   /*
2648    * not needed now. Provenance getVamsasProvenance(jalview.datamodel.Provenance
2649    * jprov) { jalview.datamodel.ProvenanceEntry[] entries = null; // TODO: fix
2650    * App and Action here. Provenance prov = new Provenance();
2651    * org.exolab.castor.types.Date date = new org.exolab.castor.types.Date( new
2652    * java.util.Date()); Entry provEntry;
2653    * 
2654    * if (jprov != null) { entries = jprov.getEntries(); for (int i = 0; i <
2655    * entries.length; i++) { provEntry = new Entry(); try { date = new
2656    * org.exolab.castor.types.Date(entries[i].getDate()); } catch (Exception ex)
2657    * { ex.printStackTrace();
2658    * 
2659    * date = new org.exolab.castor.types.Date(entries[i].getDate()); }
2660    * provEntry.setDate(date); provEntry.setUser(entries[i].getUser());
2661    * provEntry.setAction(entries[i].getAction()); prov.addEntry(provEntry); } }
2662    * else { provEntry = new Entry(); provEntry.setDate(date);
2663    * provEntry.setUser(System.getProperty("user.name")); // TODO: ext string
2664    * provEntry.setApp("JVAPP"); // TODO: ext string provEntry.setAction(action);
2665    * prov.addEntry(provEntry); }
2666    * 
2667    * return prov; }
2668    */
2669   jalview.datamodel.Provenance getJalviewProvenance(Provenance prov)
2670   {
2671     // TODO: fix App and Action entries and check use of provenance in jalview.
2672     jalview.datamodel.Provenance jprov = new jalview.datamodel.Provenance();
2673     for (int i = 0; i < prov.getEntryCount(); i++)
2674     {
2675       jprov.addEntry(prov.getEntry(i).getUser(),
2676               prov.getEntry(i).getAction(), prov.getEntry(i).getDate(),
2677               prov.getEntry(i).getId());
2678     }
2679
2680     return jprov;
2681   }
2682
2683   /**
2684    * 
2685    * @return default initial provenance list for a Jalview created vamsas
2686    *         object.
2687    */
2688   Provenance dummyProvenance()
2689   {
2690     return dummyProvenance(null);
2691   }
2692
2693   Entry dummyPEntry(String action)
2694   {
2695     Entry entry = new Entry();
2696     entry.setApp(this.provEntry.getApp());
2697     if (action != null)
2698     {
2699       entry.setAction(action);
2700     }
2701     else
2702     {
2703       entry.setAction("created.");
2704     }
2705     entry.setDate(new java.util.Date());
2706     entry.setUser(this.provEntry.getUser());
2707     return entry;
2708   }
2709
2710   Provenance dummyProvenance(String action)
2711   {
2712     Provenance prov = new Provenance();
2713     prov.addEntry(dummyPEntry(action));
2714     return prov;
2715   }
2716
2717   Entry addProvenance(Provenance p, String action)
2718   {
2719     Entry dentry = dummyPEntry(action);
2720     p.addEntry(dentry);
2721     return dentry;
2722   }
2723
2724   public Entry getProvEntry()
2725   {
2726     return provEntry;
2727   }
2728
2729   public IClientDocument getClientDocument()
2730   {
2731     return cdoc;
2732   }
2733
2734   public IdentityHashMap getJvObjectBinding()
2735   {
2736     return jv2vobj;
2737   }
2738
2739   public Hashtable getVamsasObjectBinding()
2740   {
2741     return vobj2jv;
2742   }
2743
2744   public void storeSequenceMappings(AlignmentViewport viewport,
2745           String title) throws Exception
2746   {
2747     AlignmentViewport av = viewport;
2748     try
2749     {
2750       jalview.datamodel.AlignmentI jal = av.getAlignment();
2751       // /////////////////////////////////////////
2752       // SAVE THE DATASET
2753       DataSet dataset = null;
2754       if (jal.getDataset() == null)
2755       {
2756         Cache.log.warn("Creating new dataset for an alignment.");
2757         jal.setDataset(null);
2758       }
2759       dataset = (DataSet) ((Alignment) getjv2vObj(
2760               viewport.getSequenceSetId())).getV_parent(); // jal.getDataset());
2761       if (dataset == null)
2762       {
2763         dataset = (DataSet) getjv2vObj(jal.getDataset());
2764         Cache.log.error(
2765                 "Can't find the correct dataset for the alignment in this view. Creating new one.");
2766
2767       }
2768       // Store any sequence mappings.
2769       List<AlignedCodonFrame> cframes = av.getAlignment().getCodonFrames();
2770       if (cframes != null)
2771       {
2772         for (AlignedCodonFrame acf : cframes)
2773         {
2774           if (acf.getdnaSeqs() != null && acf.getdnaSeqs().length > 0)
2775           {
2776             jalview.datamodel.SequenceI[] dmps = acf.getdnaSeqs();
2777             jalview.datamodel.Mapping[] mps = acf.getProtMappings();
2778             for (int smp = 0; smp < mps.length; smp++)
2779             {
2780               uk.ac.vamsas.objects.core.SequenceType mfrom = (SequenceType) getjv2vObj(
2781                       dmps[smp]);
2782               if (mfrom != null)
2783               {
2784                 new jalview.io.vamsas.Sequencemapping(this, mps[smp], mfrom,
2785                         dataset);
2786               }
2787               else
2788               {
2789                 Cache.log.warn(
2790                         "NO Vamsas Binding for local sequence! NOT CREATING MAPPING FOR "
2791                                 + dmps[smp].getDisplayId(true) + " to "
2792                                 + mps[smp].getTo().getName());
2793               }
2794             }
2795           }
2796         }
2797       }
2798     } catch (Exception e)
2799     {
2800       throw new Exception(MessageManager.formatMessage(
2801               "exception.couldnt_store_sequence_mappings", new String[]
2802               { title }), e);
2803     }
2804   }
2805
2806   public void clearSkipList()
2807   {
2808     if (skipList != null)
2809     {
2810       skipList.clear();
2811     }
2812   }
2813
2814   /**
2815    * @return the skipList
2816    */
2817   public Hashtable getSkipList()
2818   {
2819     return skipList;
2820   }
2821
2822   /**
2823    * @param skipList
2824    *          the skipList to set
2825    */
2826   public void setSkipList(Hashtable skipList)
2827   {
2828     this.skipList = skipList;
2829   }
2830
2831   /**
2832    * registry for datastoreItems
2833    */
2834   DatastoreRegistry dsReg = new DatastoreRegistry();
2835
2836   public DatastoreRegistry getDatastoreRegisty()
2837   {
2838     if (dsReg == null)
2839     {
2840       dsReg = new DatastoreRegistry();
2841     }
2842     return dsReg;
2843   }
2844 }