3c6e4414f39c1fac76478c2b2be69941c4b00c44
[jalview.git] / src / jalview / io / VamsasAppDatastore.java
1 /*
2  * Jalview - A Sequence Alignment Editor and Viewer
3  * Copyright (C) 2005 AM Waterhouse, J Procter, G Barton, M Clamp, S Searle
4  *
5  * This program is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU General Public License
7  * as published by the Free Software Foundation; either version 2
8  * of the License, or (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program; if not, write to the Free Software
17  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
18  */
19
20 package jalview.io;
21
22 import jalview.bin.Cache;
23 import jalview.datamodel.AlignmentAnnotation;
24 import jalview.datamodel.AlignmentI;
25 import jalview.datamodel.AlignmentView;
26 import jalview.datamodel.DBRefEntry;
27 import jalview.datamodel.SequenceFeature;
28 import jalview.datamodel.SequenceI;
29 import jalview.gui.AlignFrame;
30 import jalview.gui.AlignViewport;
31 import jalview.gui.Desktop;
32 import jalview.gui.TreePanel;
33
34 import java.util.HashMap;
35 import java.util.Hashtable;
36 import java.util.IdentityHashMap;
37 import java.util.Vector;
38
39 import uk.ac.vamsas.client.*;
40 import uk.ac.vamsas.objects.core.*;
41
42 /*
43  *
44  * static {
45  * org.exolab.castor.util.LocalConfiguration.getInstance().getProperties().setProperty(
46  * "org.exolab.castor.serializer", "org.apache.xml.serialize.XMLSerilazizer"); }
47  *
48  */
49
50 public class VamsasAppDatastore
51 {
52   Entry provEntry = null;
53
54   // AlignViewport av;
55
56   org.exolab.castor.types.Date date = new org.exolab.castor.types.Date(
57       new java.util.Date());
58
59   IClientDocument cdoc;
60
61   Hashtable vobj2jv;
62
63   IdentityHashMap jv2vobj;
64
65   public VamsasAppDatastore(IClientDocument cdoc, Hashtable vobj2jv,
66                          IdentityHashMap jv2vobj, Entry provEntry)
67   {
68     this.cdoc = cdoc;
69     this.vobj2jv = vobj2jv;
70     this.jv2vobj = jv2vobj;
71     this.provEntry = provEntry;
72   }
73
74   /*
75    * public void storeJalview(String file, AlignFrame af) { try { // 1. Load the
76    * mapping information from the file Mapping map = new
77    * Mapping(getClass().getClassLoader()); java.net.URL url =
78    * getClass().getResource("/jalview_mapping.xml"); map.loadMapping(url); // 2.
79    * Unmarshal the data // Unmarshaller unmar = new Unmarshaller();
80    * //unmar.setIgnoreExtraElements(true); //unmar.setMapping(map); // uni =
81    * (UniprotFile) unmar.unmarshal(new FileReader(file)); // 3. marshal the data
82    * with the total price back and print the XML in the console Marshaller
83    * marshaller = new Marshaller( new FileWriter(file) );
84    *
85    * marshaller.setMapping(map); marshaller.marshal(af); } catch (Exception e) {
86    * e.printStackTrace(); } }
87    *
88    *
89    */
90   /**
91    * @return the Vobject bound to Jalview datamodel object
92    */
93   protected Vobject getjv2vObj(Object jvobj)
94   {
95     if (jv2vobj.containsKey(jvobj))
96     {
97       return cdoc.getObject( (VorbaId) jv2vobj.get(jvobj));
98     }
99     if (Cache.log.isDebugEnabled())
100     {
101       Cache.log.debug("Returning null VorbaID binding for jalview object "+jvobj);
102     }
103     return null;
104   }
105
106   /**
107    *
108    * @param vobj
109    * @return Jalview datamodel object bound to the vamsas document object
110    */
111   protected Object getvObj2jv(uk.ac.vamsas.client.Vobject vobj)
112   {
113     VorbaId id = vobj.getVorbaId();
114     if (id == null)
115     {
116       id = cdoc.registerObject(vobj);
117       Cache.log
118           .debug("Registering new object and returning null for getvObj2jv");
119       return null;
120     }
121     if (vobj2jv.containsKey(vobj.getVorbaId()))
122     {
123       return vobj2jv.get(vobj.getVorbaId());
124     }
125     return null;
126   }
127
128   protected void bindjvvobj(Object jvobj, uk.ac.vamsas.client.Vobject vobj)
129   {
130     VorbaId id = vobj.getVorbaId();
131     if (id == null)
132     {
133       id = cdoc.registerObject(vobj);
134       if (id == null || vobj.getVorbaId() == null || cdoc.getObject(id)!=vobj)
135       {
136         Cache.log.error("Failed to get id for " +
137                         (vobj.isRegisterable() ? "registerable" :
138                          "unregisterable") + " object " + vobj);
139       }
140     }
141
142     if (vobj2jv.containsKey(vobj.getVorbaId()) &&
143         ! ( (VorbaId) vobj2jv.get(vobj.getVorbaId())).equals(jvobj))
144     {
145       Cache.log.debug("Warning? Overwriting existing vamsas id binding for " +
146                       vobj.getVorbaId(),
147                       new Exception("Overwriting vamsas id binding."));
148     }
149     else if (jv2vobj.containsKey(jvobj) &&
150              ! ( (VorbaId) jv2vobj.get(jvobj)).equals(vobj.getVorbaId()))
151     {
152       Cache.log.debug(
153           "Warning? Overwriting existing jalview object binding for " + jvobj,
154           new Exception("Overwriting jalview object binding."));
155     }
156     /* Cache.log.error("Attempt to make conflicting object binding! "+vobj+" id " +vobj.getVorbaId()+" already bound to "+getvObj2jv(vobj)+" and "+jvobj+" already bound to "+getjv2vObj(jvobj),new Exception("Excessive call to bindjvvobj"));
157          }*/
158     // we just update the hash's regardless!
159     Cache.log.debug("Binding "+vobj.getVorbaId()+" to "+jvobj);
160     vobj2jv.put(vobj.getVorbaId(), jvobj);
161     // JBPNote - better implementing a hybrid invertible hash.
162     jv2vobj.put(jvobj, vobj.getVorbaId());
163   }
164
165   /**
166    * put the alignment viewed by AlignViewport into cdoc.
167    *
168    * @param av alignViewport to be stored
169    * @param aFtitle title for alignment
170    */
171   public void storeVAMSAS(AlignViewport av, String aFtitle)
172   {
173     try
174     {
175       jalview.datamodel.AlignmentI jal = av.getAlignment();
176       boolean nw = false;
177       VAMSAS root = null; // will be resolved based on Dataset Parent.
178       // /////////////////////////////////////////
179       // SAVE THE DATASET
180       if (jal.getDataset() == null)
181       {
182         Cache.log.warn("Creating new dataset for an alignment.");
183         jal.setDataset(null);
184       }
185       DataSet dataset = (DataSet) getjv2vObj(jal.getDataset());
186       if (dataset == null)
187       {
188         root = cdoc.getVamsasRoots()[0]; // default vamsas root for modifying.
189         dataset = new DataSet();
190         root.addDataSet(dataset);
191         bindjvvobj(jal.getDataset(), dataset);
192         dataset.setProvenance(dummyProvenance());
193         dataset.getProvenance().addEntry(provEntry);
194         nw = true;
195       }
196       else
197       {
198         root = (VAMSAS) dataset.getV_parent();
199       }
200       // update dataset
201       Sequence sequence;
202       DbRef dbref;
203       // set new dataset and alignment sequences based on alignment Nucleotide
204       // flag.
205       // this *will* break when alignment contains both nucleotide and amino
206       // acid sequences.
207       String dict = jal.isNucleotide() ?
208           uk.ac.vamsas.objects.utils.SymbolDictionary.STANDARD_NA
209           : uk.ac.vamsas.objects.utils.SymbolDictionary.STANDARD_AA;
210       for (int i = 0; i < jal.getHeight(); i++)
211       {
212         SequenceI sq = jal.getSequenceAt(i).getDatasetSequence(); // only insert
213         // referenced
214         // sequences
215         // to dataset.
216         sequence = (Sequence) getjv2vObj(sq);
217         if (sequence == null)
218         {
219           sequence = new Sequence();
220           bindjvvobj(sq, sequence);
221           sq.setVamsasId(sequence.getVorbaId().getId());
222           sequence.setSequence(sq.getSequenceAsString());
223           sequence.setDictionary(dict);
224           sequence.setName(jal.getDataset().getSequenceAt(i).getName());
225           sequence.setStart(jal.getDataset().getSequenceAt(i).getStart());
226           sequence.setEnd(jal.getDataset().getSequenceAt(i).getEnd());
227           dataset.addSequence(sequence);
228         }
229         else
230         {
231           // verify principal attributes. and update any new
232           // features/references.
233           System.out.println("update dataset sequence object.");
234         }
235         if (sq.getSequenceFeatures() != null)
236         {
237           int sfSize = sq.getSequenceFeatures().length;
238
239           for (int sf = 0; sf < sfSize; sf++)
240           {
241             jalview.datamodel.SequenceFeature feature = (jalview.datamodel.
242                 SequenceFeature) sq
243                 .getSequenceFeatures()[sf];
244
245             DataSetAnnotations dsa = (DataSetAnnotations) getjv2vObj(feature);
246             if (dsa == null)
247             {
248               dsa = (DataSetAnnotations) getDSAnnotationFromJalview(
249                   new DataSetAnnotations(), feature);
250               if (dsa.getProvenance() == null)
251               {
252                 dsa.setProvenance(new Provenance());
253               }
254               addProvenance(dsa.getProvenance(), "created"); // JBPNote - need
255               // to update
256               dsa.setSeqRef(sequence);
257               bindjvvobj(feature, dsa);
258               dataset.addDataSetAnnotations(dsa);
259             }
260             else
261             {
262               // todo: verify and update dataset annotations for sequence
263               System.out.println("update dataset sequence annotations.");
264             }
265           }
266         }
267
268         if (sq.getDBRef() != null)
269         {
270           DBRefEntry[] entries = sq.getDBRef();
271           jalview.datamodel.DBRefEntry dbentry;
272           for (int db = 0; db < entries.length; db++)
273           {
274             dbentry = entries[db];
275             dbref = (DbRef) getjv2vObj(dbentry);
276             if (dbref == null)
277             {
278               dbref = new DbRef();
279               bindjvvobj(dbentry, dbref);
280               dbref.setAccessionId(dbentry.getAccessionId());
281               dbref.setSource(dbentry.getSource());
282               dbref.setVersion(dbentry.getVersion());
283               /*
284                * TODO: Maps are not yet supported by Jalview. Map vMap = new
285                * Map(); vMap.set dbref.addMap(vMap);
286                */
287               sequence.addDbRef(dbref);
288             }
289             else
290             {
291               // TODO: verify and update dbrefs in vamsas document
292               // there will be trouble when a dataset sequence is modified to
293               // contain more residues than were originally referenced - we must
294               // then make a number of dataset sequence entries
295               System.out
296                   .println("update dataset sequence database references.");
297             }
298           }
299
300         }
301       }
302       // dataset.setProvenance(getVamsasProvenance(jal.getDataset().getProvenance()));
303       // ////////////////////////////////////////////
304
305       // ////////////////////////////////////////////
306       // Save the Alignments
307
308       Alignment alignment = (Alignment) getjv2vObj(av); // this is so we can get the alignviewport back
309       if (alignment == null)
310       {
311         alignment = new Alignment();
312         bindjvvobj(av, alignment);
313         if (alignment.getProvenance() == null)
314         {
315           alignment.setProvenance(new Provenance());
316         }
317         addProvenance(alignment.getProvenance(), "added"); // TODO: insert some
318         // sensible source
319         // here
320         dataset.addAlignment(alignment);
321         {
322           Property title = new Property();
323           title.setName("jalview:AlTitle");
324           title.setType("string");
325           title.setContent(aFtitle);
326           alignment.addProperty(title);
327         }
328         alignment.setGapChar(String.valueOf(av.getGapCharacter()));
329         AlignmentSequence alseq = null;
330         for (int i = 0; i < jal.getHeight(); i++)
331         {
332           alseq = new AlignmentSequence();
333           // TODO: VAMSAS: translate lowercase symbols to annotation ?
334           alseq.setSequence(jal.getSequenceAt(i).getSequenceAsString());
335           alseq.setName(jal.getSequenceAt(i).getName());
336           alseq.setStart(jal.getSequenceAt(i).getStart());
337           alseq.setEnd(jal.getSequenceAt(i).getEnd());
338           if (getjv2vObj(jal.getSequenceAt(i).getDatasetSequence())==null)
339           {
340             Cache.log.warn("Serious. Unbound dataset sequence in alignment: "+jal.getSequenceAt(i).getDatasetSequence());
341           }
342           alseq.setRefid(getjv2vObj(jal.getSequenceAt(i).getDatasetSequence()));
343           alignment.addAlignmentSequence(alseq);
344           bindjvvobj(jal.getSequenceAt(i), alseq);
345         }
346       }
347       else
348       {
349         // todo: verify and update mutable alignment props.
350         if (alignment.getModifiable()==null) // TODO: USE VAMSAS LIBRARY OBJECT LOCK METHODS
351         {
352           System.out.println("update alignment in document.");
353         }
354         else
355         {
356           System.out
357               .println("update edited alignment to new alignment in document.");
358         }
359       }
360       // ////////////////////////////////////////////
361       // SAVE Alignment Sequence Features
362       for (int i = 0, iSize = alignment.getAlignmentSequenceCount(); i < iSize;
363            i++)
364       {
365         AlignmentSequence valseq;
366         SequenceI alseq = (SequenceI) getvObj2jv(valseq = alignment
367                                                  .getAlignmentSequence(i));
368         if (alseq != null && alseq.getSequenceFeatures() != null)
369         {
370           jalview.datamodel.SequenceFeature[] features = alseq
371               .getSequenceFeatures();
372           for (int f = 0; f < features.length; f++)
373           {
374             if (features[f] != null)
375             {
376               AlignmentSequenceAnnotation valseqf = (
377                   AlignmentSequenceAnnotation) getjv2vObj(features[i]);
378               if (valseqf == null)
379               {
380
381                 valseqf = (AlignmentSequenceAnnotation)
382                     getDSAnnotationFromJalview(
383                         new AlignmentSequenceAnnotation(), features[i]);
384                 if (valseqf.getProvenance() == null)
385                 {
386                   valseqf.setProvenance(new Provenance());
387                 }
388                 addProvenance(valseqf.getProvenance(), "created"); // JBPNote -
389                 // need to
390                 // update
391                 bindjvvobj(features[i], valseqf);
392                 valseq.addAlignmentSequenceAnnotation(valseqf);
393               }
394             }
395
396           }
397         }
398       }
399
400       // ////////////////////////////////////////////
401       // SAVE ANNOTATIONS
402       if (jal.getAlignmentAnnotation() != null)
403       {
404         jalview.datamodel.AlignmentAnnotation[] aa = jal
405             .getAlignmentAnnotation();
406         java.util.HashMap AlSeqMaps = new HashMap(); // stores int maps from
407         // alignment columns to
408         // sequence positions.
409         for (int i = 0; i < aa.length; i++)
410         {
411           if (aa[i] == null || isJalviewOnly(aa[i]))
412           {
413             continue;
414           }
415           if (aa[i].sequenceRef != null)
416           {
417             uk.ac.vamsas.objects.core.AlignmentSequence alsref = (uk.ac.vamsas.
418                 objects.core.AlignmentSequence) getjv2vObj(aa[i].sequenceRef);
419             uk.ac.vamsas.objects.core.AlignmentSequenceAnnotation an = (uk.ac.
420                 vamsas.objects.core.AlignmentSequenceAnnotation) getjv2vObj(aa[
421                 i]);
422             int[] gapMap = null;
423             if (AlSeqMaps.containsKey(aa[i].sequenceRef))
424             {
425               gapMap = (int[]) AlSeqMaps.get(aa[i].sequenceRef);
426             }
427             else
428             {
429               gapMap = new int[aa[i].sequenceRef.getLength()];
430               // map from alignment position to sequence position.
431               int[] sgapMap = aa[i].sequenceRef.gapMap();
432               for (int a = 0; a < sgapMap.length; a++)
433               {
434                 gapMap[sgapMap[a]] = a;
435               }
436             }
437             if (an == null)
438             {
439               an = new uk.ac.vamsas.objects.core.AlignmentSequenceAnnotation();
440               Seg vSeg = new Seg();
441               vSeg.setStart(1);
442               vSeg.setInclusive(true);
443               vSeg.setEnd(gapMap.length);
444               an.addSeg(vSeg);
445               an.setType("jalview:SecondaryStructurePrediction"); // TODO: better fix this rough guess ;)
446               alsref.addAlignmentSequenceAnnotation(an);
447               bindjvvobj(aa[i], an);
448               // LATER: much of this is verbatim from the alignmentAnnotation
449               // method below. suggests refactoring to make rangeAnnotation the
450               // base class
451               an.setDescription(aa[i].description);
452               if (aa[i].graph > 0)
453               {
454                 an.setGraph(true); // aa[i].graph);
455               }
456               else
457               {
458                 an.setGraph(false);
459               }
460               an.setLabel(aa[i].label);
461               an.setProvenance(dummyProvenance()); // get provenance as user
462               // created, or jnet, or
463               // something else.
464               an.setGroup(Integer.toString(aa[i].graphGroup)); // // JBPNote -
465               // originally we
466               // were going to
467               // store
468               // graphGroup in
469               // the Jalview
470               // specific
471               // bits.
472               AnnotationElement ae;
473               for (int a = 0; a < aa[i].annotations.length; a++)
474               {
475                 if (aa[i].annotations[a] == null)
476                 {
477                   continue;
478                 }
479
480                 ae = new AnnotationElement();
481                 ae.setDescription(aa[i].annotations[a].description);
482                 ae.addGlyph(new Glyph());
483                 ae.getGlyph(0)
484                     .setContent(aa[i].annotations[a].displayCharacter); // assume
485                 // jax-b
486                 // takes
487                 // care
488                 // of
489                 // utf8
490                 // translation
491                 if (aa[i].graph !=
492                     jalview.datamodel.AlignmentAnnotation.NO_GRAPH)
493                 {
494                   ae.addValue(aa[i].annotations[a].value);
495                 }
496                 ae.setPosition(gapMap[a] + 1); // position w.r.t. AlignmentSequence
497                 // symbols
498                 if (aa[i].annotations[a].secondaryStructure != ' ')
499                 {
500                   // we only write an annotation where it really exists.
501                   Glyph ss = new Glyph();
502                   ss
503                       .setDict(uk.ac.vamsas.objects.utils.GlyphDictionary.
504                                PROTEIN_SS_3STATE);
505                   ss.setContent(String
506                                 .valueOf(aa[i].annotations[a].
507                                          secondaryStructure));
508                   ae.addGlyph(ss);
509                 }
510                 an.addAnnotationElement(ae);
511               }
512             }
513             else
514             {
515               // update reference sequence Annotation
516               if (an.getModifiable()==null) // TODO: USE VAMSAS LIBRARY OBJECT LOCK METHODS)
517               {
518                 // verify existing alignment sequence annotation is up to date
519                 System.out.println("update alignment sequence annotation.");
520               }
521               else
522               {
523                 // verify existing alignment sequence annotation is up to date
524                 System.out
525                     .println(
526                     "make new alignment sequence annotation if modification has happened.");
527               }
528             }
529           }
530           else
531           {
532             // add Alignment Annotation
533             uk.ac.vamsas.objects.core.AlignmentAnnotation an = (uk.ac.vamsas.
534                 objects.core.AlignmentAnnotation) getjv2vObj(aa[i]);
535             if (an == null)
536             {
537               an = new uk.ac.vamsas.objects.core.AlignmentAnnotation();
538               an.setType("jalview:AnnotationRow");
539               an.setDescription(aa[i].description);
540               alignment.addAlignmentAnnotation(an);
541               Seg vSeg = new Seg();
542               vSeg.setStart(1);
543               vSeg.setInclusive(true);
544               vSeg.setEnd(jal.getWidth());
545               an.addSeg(vSeg);
546               if (aa[i].graph > 0)
547               {
548                 an.setGraph(true); // aa[i].graph);
549               }
550               an.setLabel(aa[i].label);
551               an.setProvenance(dummyProvenance());
552               if (aa[i].graph != aa[i].NO_GRAPH)
553               {
554                 an.setGroup(Integer.toString(aa[i].graphGroup)); // // JBPNote -
555                 // originally we
556                 // were going to
557                 // store
558                 // graphGroup in
559                 // the Jalview
560                 // specific
561                 // bits.
562                 an.setGraph(true);
563               }
564               else
565               {
566                 an.setGraph(false);
567               }
568               AnnotationElement ae;
569
570               for (int a = 0; a < aa[i].annotations.length; a++)
571               {
572                 if ( (aa[i] == null) || (aa[i].annotations[a] == null))
573                 {
574                   continue;
575                 }
576
577                 ae = new AnnotationElement();
578                 ae.setDescription(aa[i].annotations[a].description);
579                 ae.addGlyph(new Glyph());
580                 ae.getGlyph(0)
581                     .setContent(aa[i].annotations[a].displayCharacter); // assume
582                 // jax-b
583                 // takes
584                 // care
585                 // of
586                 // utf8
587                 // translation
588                 ae.addValue(aa[i].annotations[a].value);
589                 ae.setPosition(a + 1);
590                 if (aa[i].annotations[a].secondaryStructure != ' ')
591                 {
592                   Glyph ss = new Glyph();
593                   ss
594                       .setDict(uk.ac.vamsas.objects.utils.GlyphDictionary.
595                                PROTEIN_SS_3STATE);
596                   ss.setContent(String
597                                 .valueOf(aa[i].annotations[a].
598                                          secondaryStructure));
599                   ae.addGlyph(ss);
600                 }
601                 an.addAnnotationElement(ae);
602               }
603               if (aa[i].editable)
604               {
605                 //an.addProperty(newProperty("jalview:editable", null, "true"));
606                 // an.setModifiable(""); // TODO: This is not the way the modifiable flag is supposed to be used.
607               }
608               if (aa[i].graph != jalview.datamodel.AlignmentAnnotation.NO_GRAPH)
609               {
610                 an.setGraph(true);
611                 an.setGroup(Integer.toString(aa[i].graphGroup));
612                 an.addProperty(newProperty("jalview:graphType", null,
613                                            ( (aa[i].graph ==
614                                               jalview.datamodel.AlignmentAnnotation.
615                                               BAR_GRAPH) ? "BAR_GRAPH" :
616                                             "LINE_GRAPH")));
617
618                 /** and on and on..
619                  vProperty=new Property();
620                   vProperty.setName("jalview:graphThreshhold");
621                   vProperty.setContent(aa[i].threshold);
622                  */
623
624               }
625             }
626             else
627             {
628               if (an.getModifiable()==null) // TODO: USE VAMSAS LIBRARY OBJECT LOCK METHODS)
629               {
630                 // verify annotation - update (perhaps)
631                 Cache.log.info(
632                     "update alignment sequence annotation. not yet implemented.");
633               }
634               else
635               {
636                 // verify annotation - update (perhaps)
637                 Cache.log.info("updated alignment sequence annotation added.");
638               }
639             }
640           }
641         }
642       }
643       // /////////////////////////////////////////////////////
644
645       // //////////////////////////////////////////////
646       // /SAVE THE TREES
647       // /////////////////////////////////
648       // FIND ANY ASSOCIATED TREES
649       if (Desktop.desktop != null)
650       {
651         javax.swing.JInternalFrame[] frames = Desktop.instance.getAllFrames();
652
653         for (int t = 0; t < frames.length; t++)
654         {
655           if (frames[t] instanceof TreePanel)
656           {
657             TreePanel tp = (TreePanel) frames[t];
658
659             if (tp.getAlignment() == jal)
660             {
661               Tree tree = (Tree) getjv2vObj(tp);
662               if (tree == null)
663               {
664                 tree = new Tree();
665                 bindjvvobj(tp, tree);
666                 tree.setTitle(tp.getTitle());
667                 Newick newick = new Newick();
668                 // TODO: translate sequenceI to leaf mappings to vamsas
669                 // references - see tree specification in schema.
670                 newick.setContent(tp.getTree().toString());
671                 newick.setTitle(tp.getTitle());
672                 tree.addNewick(newick);
673                 tree.setProvenance(makeTreeProvenance(jal, tp));
674                 alignment.addTree(tree);
675               }
676               else
677               {
678                 if (tree.getModifiable()==null) // TODO: USE VAMSAS LIBRARY OBJECT LOCK METHODS)
679                 {
680                   // verify any changes.
681                   System.out.println("Update tree in document.");
682                 }
683                 else
684                 {
685                   System.out
686                       .println("Add modified tree as new tree in document.");
687                 }
688               }
689             }
690           }
691         }
692       }
693       // Store Jalview specific stuff in the Jalview appData
694       // not implemented in the SimpleDoc interface.
695     }
696
697     catch (Exception ex)
698     {
699       ex.printStackTrace();
700     }
701
702   }
703
704   private Property newProperty(String name, String type, String content)
705   {
706     Property vProperty = new Property();
707     vProperty.setName(name);
708     if (type != null)
709     {
710       vProperty.setType(type);
711     }
712     else
713     {
714       vProperty.setType("String");
715     }
716     vProperty.setContent(content);
717     return vProperty;
718   }
719
720   /**
721    * correctly create a RangeAnnotation from a jalview sequence feature
722    *
723    * @param dsa
724    *          (typically DataSetAnnotations or AlignmentSequenceAnnotation)
725    * @param feature
726    *          (the feature to be mapped from)
727    * @return
728    */
729   private RangeAnnotation getDSAnnotationFromJalview(RangeAnnotation dsa,
730       SequenceFeature feature)
731   {
732     dsa.setType(feature.getType());
733     Seg vSeg = new Seg();
734     vSeg.setStart(feature.getBegin());
735     vSeg.setEnd(feature.getEnd());
736     vSeg.setInclusive(true);
737     dsa.addSeg(vSeg);
738     dsa.setDescription(feature.getDescription());
739     dsa.setStatus(feature.getStatus());
740     if (feature.links != null && feature.links.size() > 0)
741     {
742       for (int i = 0, iSize = feature.links.size(); i < iSize; i++)
743       {
744         String link = (String) feature.links.elementAt(i);
745         int sep = link.indexOf('|');
746         if (sep > -1)
747         {
748           Link vLink = new Link();
749           if (sep > 0)
750           {
751             vLink.setContent(link.substring(0, sep - 1));
752           }
753           else
754           {
755             vLink.setContent("");
756           }
757           vLink.setHref(link.substring(sep + 1)); // TODO: validate href.
758           dsa.addLink(vLink);
759         }
760       }
761     }
762     dsa.setGroup(feature.getFeatureGroup());
763     return dsa;
764   }
765
766   /**
767    * correctly creates provenance for trees calculated on an alignment by
768    * jalview.
769    *
770    * @param jal
771    * @param tp
772    * @return
773    */
774   private Provenance makeTreeProvenance(AlignmentI jal, TreePanel tp)
775   {
776     Provenance prov = new Provenance();
777     prov.addEntry(new Entry());
778     prov.getEntry(0).setAction("imported " + tp.getTitle());
779     prov.getEntry(0).setUser(provEntry.getUser());
780     prov.getEntry(0).setApp(provEntry.getApp());
781     prov.getEntry(0).setDate(provEntry.getDate());
782     if (tp.getTree().hasOriginalSequenceData())
783     {
784       Input vInput = new Input();
785       // LATER: check to see if tree input data is contained in this alignment -
786       // or just correctly resolve the tree's seqData to the correct alignment in
787       // the document.
788       // vInput.setObjRef(getjv2vObj(jal));
789       vInput.setObjRef(getjv2vObj(tp.getViewPort()));
790       prov.getEntry(0).setAction("created " + tp.getTitle());
791       prov.getEntry(0).addInput(vInput);
792       vInput.setName("jalview:seqdist");
793       prov.getEntry(0).addParam(new Param());
794       prov.getEntry(0).getParam(0).setName("treeType");
795       prov.getEntry(0).getParam(0).setType("utf8");
796       prov.getEntry(0).getParam(0).setContent("NJ");
797
798       int ranges[] = tp.getTree().seqData.getVisibleContigs();
799       // VisibleContigs are with respect to alignment coordinates. Still need offsets
800       int start = tp.getTree().seqData.getAlignmentOrigin();
801       for (int r = 0; r < ranges.length; r += 2)
802       {
803         Seg visSeg = new Seg();
804         visSeg.setStart(1 + start + ranges[r]);
805         visSeg.setEnd(start + ranges[r + 1]);
806         visSeg.setInclusive(true);
807         vInput.addSeg(visSeg);
808       }
809     }
810     return prov;
811   }
812
813   /**
814    *
815    * @param tp
816    * @return Object[] { AlignmentView, AlignmentI - reference alignment for
817    *         input }
818    */
819   private Object[] recoverInputData(Provenance tp)
820   {
821     for (int pe = 0; pe < tp.getEntryCount(); pe++)
822     {
823       if (tp.getEntry(pe).getInputCount() > 0)
824       {
825         if (tp.getEntry(pe).getInputCount() > 1)
826         {
827           Cache.log.warn("Ignoring additional input spec in provenance entry "
828                          + tp.getEntry(pe).toString());
829         }
830         // LATER: deal sensibly with multiple inputs.
831         Input vInput = tp.getEntry(pe).getInput(0);
832         if (vInput.getObjRef() instanceof uk.ac.vamsas.objects.core.Alignment)
833         {
834           // recover an AlignmentView for the input data
835           AlignViewport javport = (AlignViewport) getvObj2jv( (uk.ac.vamsas.
836               client.Vobject) vInput
837               .getObjRef());
838           jalview.datamodel.AlignmentI jal = javport.getAlignment();
839           jalview.datamodel.CigarArray view = javport.getAlignment().
840               getCompactAlignment();
841           int from = 1, to = jal.getWidth();
842           int offset = 0; // deleteRange modifies its frame of reference
843           for (int r = 0, s = vInput.getSegCount(); r < s; r++)
844           {
845             Seg visSeg = vInput.getSeg(r);
846             int se[] = getSegRange(visSeg, true); // jalview doesn't do bidirection alignments yet.
847             if (to < se[1])
848             {
849               Cache.log.warn("Ignoring invalid segment in InputData spec.");
850             }
851             else
852             {
853               if (se[0] > from)
854               {
855                 view.deleteRange(offset + from - 1, offset + se[0] - 2);
856                 offset -= se[0] - from;
857               }
858               from = se[1] + 1;
859             }
860           }
861           if (from < to)
862           {
863             view.deleteRange(offset + from - 1, offset + to - 1); // final deletion - TODO: check off by
864             // one for to
865           }
866           return new Object[]
867               {
868               new AlignmentView(view), jal};
869         }
870       }
871     }
872     Cache.log.debug("Returning null for input data recovery from provenance.");
873     return null;
874   }
875
876   /**
877    * get start<end range of segment, adjusting for inclusivity flag and
878    * polarity.
879    *
880    * @param visSeg
881    * @param ensureDirection when true - always ensure start is less than end.
882    * @return int[] { start, end, direction} where direction==1 for range running from end to start.
883    */
884   private int[] getSegRange(Seg visSeg, boolean ensureDirection)
885   {
886     boolean incl = visSeg.getInclusive();
887     // adjust for inclusive flag.
888     int pol = (visSeg.getStart() <= visSeg.getEnd()) ? 1 : -1; // polarity of
889     // region.
890     int start = visSeg.getStart() + (incl ? 0 : pol);
891     int end = visSeg.getEnd() + (incl ? 0 : -pol);
892     if (ensureDirection && pol == -1)
893     {
894       // jalview doesn't deal with inverted ranges, yet.
895       int t = end;
896       end = start;
897       start = t;
898     }
899     return new int[]
900         {
901         start, end, pol < 0 ? 1 : 0};
902   }
903
904   /**
905    *
906    * @param annotation
907    * @return true if annotation is not to be stored in document
908    */
909   private boolean isJalviewOnly(AlignmentAnnotation annotation)
910   {
911     return annotation.label.equals("Quality")
912         || annotation.label.equals("Conservation")
913         || annotation.label.equals("Consensus");
914   }
915
916   /**
917    * This will return the first AlignFrame viewing AlignViewport av.
918    * It will break if there are more than one AlignFrames viewing a particular av.
919    * This also shouldn't be in the io package.
920    * @param av
921    * @return alignFrame for av
922    */
923   public AlignFrame getAlignFrameFor(AlignViewport av)
924   {
925     if (Desktop.desktop != null)
926     {
927       javax.swing.JInternalFrame[] frames = Desktop.instance.getAllFrames();
928
929       for (int t = 0; t < frames.length; t++)
930       {
931         if (frames[t] instanceof AlignFrame)
932         {
933           if ( ( (AlignFrame) frames[t]).getViewport() == av)
934           {
935             return (AlignFrame) frames[t];
936           }
937         }
938       }
939     }
940     return null;
941   }
942
943   public void updateToJalview()
944   {
945     VAMSAS _roots[] = cdoc.getVamsasRoots();
946
947     for (int _root = 0; _root < _roots.length; _root++)
948     {
949       VAMSAS root = _roots[_root];
950       boolean newds = false;
951       for (int _ds = 0, _nds = root.getDataSetCount(); _ds < _nds; _ds++)
952       {
953         // ///////////////////////////////////
954         // ///LOAD DATASET
955         DataSet dataset = root.getDataSet(_ds);
956         int i, iSize = dataset.getSequenceCount();
957         Vector dsseqs;
958         jalview.datamodel.Alignment jdataset = (jalview.datamodel.Alignment)
959             getvObj2jv(dataset);
960         int jremain = 0;
961         if (jdataset == null)
962         {
963           Cache.log.debug("Initialising new jalview dataset fields");
964           newds = true;
965           dsseqs = new Vector();
966         }
967         else
968         {
969           Cache.log.debug("Update jalview dataset from vamsas.");
970           jremain = jdataset.getHeight();
971           dsseqs = jdataset.getSequences();
972         }
973
974         // TODO: test sequence merging - we preserve existing non vamsas
975         // sequences but add in any new vamsas ones, and don't yet update any
976         // sequence attributes
977         for (i = 0; i < iSize; i++)
978         {
979           Sequence vdseq = dataset.getSequence(i);
980           jalview.datamodel.SequenceI dsseq = (SequenceI) getvObj2jv(vdseq);
981           if (dsseq != null)
982           {
983             if (!dsseq.getSequenceAsString().equals(vdseq.getSequence()))
984             {
985               throw new Error(
986                   "Broken! - mismatch of dataset sequence: and jalview internal dataset sequence.");
987             }
988             jremain--;
989           }
990           else
991           {
992             dsseq = new jalview.datamodel.Sequence(
993                 dataset.getSequence(i).getName(),
994                 dataset.getSequence(i).getSequence(),
995                 (int)dataset.getSequence(i).getStart(),
996                 (int)dataset.getSequence(i).getEnd());
997             dsseq.setDescription(dataset.getSequence(i).getDescription());
998             bindjvvobj(dsseq, dataset.getSequence(i));
999             dsseq.setVamsasId(dataset.getSequence(i).getVorbaId().getId());
1000             dsseqs.add(dsseq);
1001           }
1002           if (vdseq.getDbRefCount() > 0)
1003           {
1004             DbRef[] dbref = vdseq.getDbRef();
1005             for (int db = 0; db < dbref.length; db++)
1006             {
1007               jalview.datamodel.DBRefEntry dbr = (jalview.datamodel.DBRefEntry)
1008                   getvObj2jv(dbref[db]);
1009               if (dbr == null)
1010               {
1011                 // add new dbref
1012                 dsseq.addDBRef(dbr = new jalview.datamodel.DBRefEntry
1013                                (
1014                                    dbref[db].getSource().toString(),
1015                                    dbref[db].getVersion().toString(),
1016                                    dbref[db].getAccessionId().toString()));
1017                 bindjvvobj(dbr, dbref[db]);
1018               }
1019             }
1020           }
1021         }
1022
1023         if (newds)
1024         {
1025           SequenceI[] seqs = new SequenceI[dsseqs.size()];
1026           for (i = 0, iSize = dsseqs.size(); i < iSize; i++)
1027           {
1028             seqs[i] = (SequenceI) dsseqs.elementAt(i);
1029             dsseqs.setElementAt(null, i);
1030           }
1031           jdataset = new jalview.datamodel.Alignment(seqs);
1032           Cache.log.debug("New vamsas dataset imported into jalview.");
1033           bindjvvobj(jdataset, dataset);
1034         }
1035         // ////////
1036         // add any new dataset sequence feature annotations
1037         if (dataset.getDataSetAnnotations() != null)
1038         {
1039           for (int dsa = 0; dsa < dataset.getDataSetAnnotationsCount(); dsa++)
1040           {
1041             DataSetAnnotations dseta = dataset.getDataSetAnnotations(dsa);
1042             SequenceI dsSeq = (SequenceI) getvObj2jv( (Vobject) dseta.getSeqRef());
1043             if (dsSeq == null)
1044             {
1045               jalview.bin.Cache.log.warn(
1046                   "Couldn't resolve jalview sequenceI for dataset object reference " +
1047                   ( (Vobject) dataset.getDataSetAnnotations(dsa).getSeqRef()).
1048                   getVorbaId().getId());
1049             }
1050             else
1051             {
1052               if (dseta.getAnnotationElementCount() == 0)
1053               {
1054                 jalview.datamodel.SequenceFeature sf = (jalview.datamodel.
1055                     SequenceFeature) getvObj2jv(dseta);
1056                 if (sf == null)
1057                 {
1058                   dsSeq.addSequenceFeature(sf = getJalviewSeqFeature(dseta));
1059                   bindjvvobj(sf, dseta);
1060                 }
1061               }
1062               else
1063               {
1064                 // TODO: deal with alignmentAnnotation style annotation
1065                 // appearing on dataset sequences.
1066                 // JBPNote: we could just add them to all alignments but
1067                 // that may complicate cross references in the jalview
1068                 // datamodel
1069                 Cache.log.warn("Ignoring dataset annotation with annotationElements. Not yet supported in jalview.");
1070               }
1071             }
1072           }
1073         }
1074
1075         if (dataset.getAlignmentCount() > 0)
1076         {
1077           // LOAD ALIGNMENTS from DATASET
1078
1079           for (int al = 0, nal = dataset.getAlignmentCount(); al < nal; al++)
1080           {
1081             uk.ac.vamsas.objects.core.Alignment alignment = dataset.getAlignment(
1082                 al);
1083             AlignViewport av = (AlignViewport) getvObj2jv(alignment);
1084             jalview.datamodel.AlignmentI jal = null;
1085             if (av != null)
1086             {
1087               jal = av.getAlignment();
1088             }
1089             iSize = alignment.getAlignmentSequenceCount();
1090             boolean newal = (jal == null) ? true : false;
1091             Vector newasAnnots = new Vector();
1092             char gapChar = ' '; // default for new alignments read in from the document
1093             if (jal != null)
1094             {
1095               dsseqs = jal.getSequences(); // for merge/update
1096               gapChar = jal.getGapCharacter();
1097             }
1098             else
1099             {
1100               dsseqs = new Vector();
1101             }
1102             char valGapchar = alignment.getGapChar().charAt(0);
1103             for (i = 0; i < iSize; i++)
1104             {
1105               AlignmentSequence valseq = alignment.getAlignmentSequence(i);
1106               jalview.datamodel.SequenceI alseq = (SequenceI) getvObj2jv(valseq);
1107               if (alseq != null)
1108               {
1109                 //TODO: upperCase/LowerCase situation here ? do we allow it ?
1110                 //if (!alseq.getSequence().equals(valseq.getSequence())) {
1111                 // throw new Error("Broken! - mismatch of dataset sequence and jalview internal dataset sequence.");
1112                 if (Cache.log.isDebugEnabled())
1113                 {
1114                   Cache.log.debug("Updating apparently edited sequence " +
1115                                   alseq.getName());
1116                 }
1117                 // this might go *horribly* wrong
1118                 alseq.setSequence(new String(valseq.getSequence()).replace(
1119                     valGapchar, gapChar));
1120                 jremain--;
1121               }
1122               else
1123               {
1124                 alseq = new jalview.datamodel.Sequence(
1125                     valseq.getName(),
1126                     valseq.getSequence().replace(valGapchar, gapChar),
1127                     (int)valseq.getStart(),
1128                     (int)valseq.getEnd());
1129
1130                 Vobject datsetseq = (Vobject) valseq.getRefid();
1131                 if (datsetseq != null)
1132                 {
1133                   alseq.setDatasetSequence( (SequenceI) getvObj2jv(datsetseq)); // exceptions if AlignemntSequence reference isn't a simple SequenceI
1134                 }
1135                 else
1136                 {
1137                   Cache.log.error(
1138                       "Invalid dataset sequence id (null) for alignment sequence " +
1139                       valseq.getVorbaId());
1140                 }
1141                 bindjvvobj(alseq, valseq);
1142                 alseq.setVamsasId(valseq.getVorbaId().getId());
1143                 dsseqs.add(alseq);
1144               }
1145               if (valseq.getAlignmentSequenceAnnotationCount() > 0)
1146               {
1147                 AlignmentSequenceAnnotation[] vasannot = valseq.
1148                     getAlignmentSequenceAnnotation();
1149                 for (int a = 0; a < vasannot.length; a++)
1150                 {
1151                   jalview.datamodel.AlignmentAnnotation asa = (jalview.
1152                       datamodel.AlignmentAnnotation) getvObj2jv(vasannot[a]); // TODO: 1:many jalview alignment sequence annotations
1153                   if (asa == null)
1154                   {
1155                     int se[] = getBounds(vasannot[a]);
1156                     asa = getjAlignmentAnnotation(jal, vasannot[a]);
1157                     asa.sequenceRef = alseq;
1158                     asa.createSequenceMapping(alseq, alseq.getStart() + se[0], false); // TODO: verify that positions in alseqAnnotation correspond to ungapped residue positions.
1159                     bindjvvobj(asa, vasannot[a]);
1160                     newasAnnots.add(asa);
1161                   }
1162                   else
1163                   {
1164                     // update existing annotation - can do this in place
1165                     if (vasannot[a].getModifiable()==null) // TODO: USE VAMSAS LIBRARY OBJECT LOCK METHODS)
1166                     {
1167                       Cache.log.info(
1168                           "UNIMPLEMENTED: not recovering user modifiable sequence alignment annotation");
1169                       // TODO: should at least replace with new one - otherwise things will break
1170                       // basically do this:
1171                       // int se[] = getBounds(vasannot[a]);
1172                       // asa.update(getjAlignmentAnnotation(jal, vasannot[a])); //  update from another annotation object in place.
1173                       // asa.createSequenceMapping(alseq, se[0], false);
1174
1175                     }
1176                   }
1177                 }
1178               }
1179             }
1180             if (jal == null)
1181             {
1182               SequenceI[] seqs = new SequenceI[dsseqs.size()];
1183               for (i = 0, iSize = dsseqs.size(); i < iSize; i++)
1184               {
1185                 seqs[i] = (SequenceI) dsseqs.elementAt(i);
1186                 dsseqs.setElementAt(null, i);
1187               }
1188               jal = new jalview.datamodel.Alignment(seqs);
1189               Cache.log.debug("New vamsas alignment imported into jalview " +
1190                               alignment.getVorbaId().getId());
1191               jal.setDataset(jdataset);
1192             }
1193             if (newasAnnots != null && newasAnnots.size() > 0)
1194             {
1195               // Add the new sequence annotations in to the alignment.
1196               for (int an = 0, anSize = newasAnnots.size(); an < anSize; an++)
1197               {
1198                 jal.addAnnotation( (AlignmentAnnotation) newasAnnots.elementAt(
1199                     an));
1200                 // TODO: check if anything has to be done - like calling adjustForAlignment or something.
1201                 newasAnnots.setElementAt(null, an);
1202               }
1203               newasAnnots = null;
1204             }
1205             // //////////////////////////////////////////
1206             // //LOAD ANNOTATIONS FOR THE ALIGNMENT
1207             // ////////////////////////////////////
1208             if (alignment.getAlignmentAnnotationCount() > 0)
1209             {
1210               uk.ac.vamsas.objects.core.AlignmentAnnotation[] an = alignment.
1211                   getAlignmentAnnotation();
1212
1213               for (int j = 0; j < an.length; j++)
1214               {
1215                 jalview.datamodel.AlignmentAnnotation jan = (jalview.datamodel.
1216                     AlignmentAnnotation) getvObj2jv(an[j]);
1217                 if (jan != null)
1218                 {
1219                   // update or stay the same.
1220                   // TODO: should at least replace with a new one - otherwise things will break
1221                   // basically do this:
1222                   // jan.update(getjAlignmentAnnotation(jal, an[a])); //  update from another annotation object in place.
1223
1224                   Cache.log.debug("update from vamsas alignment annotation to existing jalview alignment annotation.");
1225                   if (an[j].getModifiable()==null) // TODO: USE VAMSAS LIBRARY OBJECT LOCK METHODS)
1226                   {
1227                     // TODO: user defined annotation is totally mutable... - so load it up or throw away if locally edited.
1228                     Cache.log.info(
1229                         "NOT IMPLEMENTED - Recovering user-modifiable annotation - yet...");
1230                   }
1231                   // TODO: compare annotation element rows
1232                   // TODO: compare props.
1233                 }
1234                 else
1235                 {
1236                   jan = getjAlignmentAnnotation(jal, an[j]);
1237                   jal.addAnnotation(jan);
1238                   bindjvvobj(jan, an[j]);
1239                 }
1240               }
1241             }
1242             AlignFrame alignFrame;
1243             if (av == null)
1244             {
1245               Cache.log.debug("New alignframe for alignment " +
1246                               alignment.getVorbaId());
1247               // ///////////////////////////////
1248               // construct alignment view
1249               alignFrame = new AlignFrame(jal, AlignFrame.DEFAULT_WIDTH,
1250                                           AlignFrame.DEFAULT_HEIGHT);
1251               av = alignFrame.getViewport();
1252               String title = alignment.getProvenance().getEntry(alignment.
1253                   getProvenance().getEntryCount() - 1).getAction();
1254               if (alignment.getPropertyCount() > 0)
1255               {
1256                 for (int p = 0, pe = alignment.getPropertyCount(); p < pe; p++)
1257                 {
1258                   if (alignment.getProperty(p).getName().equals(
1259                       "jalview:AlTitle"))
1260                   {
1261                     title = alignment.getProperty(p).getContent();
1262                   }
1263                 }
1264               }
1265               // TODO: automatically create meaningful title for a vamsas alignment using its provenance.
1266               jalview.gui.Desktop.addInternalFrame(alignFrame,
1267                   title + "(" + alignment.getVorbaId() + ")",
1268                   AlignFrame.DEFAULT_WIDTH,
1269                   AlignFrame.DEFAULT_HEIGHT);
1270               bindjvvobj(av, alignment);
1271             }
1272             else
1273             {
1274               // find the alignFrame for jal.
1275               // TODO: fix this so we retrieve the alignFrame handing av *directly*
1276               alignFrame = getAlignFrameFor(av);
1277             }
1278             // LOAD TREES
1279             // /////////////////////////////////////
1280             if (alignment.getTreeCount() > 0)
1281             {
1282
1283               for (int t = 0; t < alignment.getTreeCount(); t++)
1284               {
1285                 Tree tree = alignment.getTree(t);
1286                 TreePanel tp = (TreePanel) getvObj2jv(tree);
1287                 if (tp != null)
1288                 {
1289                   Cache.log.info(
1290                       "Update from vamsas document to alignment associated tree not implemented yet.");
1291                 }
1292                 else
1293                 {
1294                   // make a new tree
1295                   Object[] idata = this.recoverInputData(tree.getProvenance());
1296                   try
1297                   {
1298                     AlignmentView inputData = null;
1299                     if (idata != null && idata[0] != null)
1300                     {
1301                       inputData = (AlignmentView) idata[0];
1302                     }
1303                     tp = alignFrame.ShowNewickTree(
1304                         new jalview.io.NewickFile(tree.getNewick(0).getContent()),
1305                         tree.getNewick(0).getTitle() + " (" + tree.getVorbaId() +
1306                         ")", inputData,
1307                         600, 500,
1308                         t * 20 + 50, t * 20 + 50);
1309                     bindjvvobj(tp, tree);
1310                   }
1311                   catch (Exception e)
1312                   {
1313                     Cache.log.warn("Problems parsing treefile '" +
1314                                    tree.getNewick(0).getContent() + "'", e);
1315                   }
1316                 }
1317               }
1318             }
1319
1320           }
1321         }
1322       }
1323     }
1324   }
1325
1326   // bitfields - should be a template in j1.5
1327   private static int HASSECSTR = 0;
1328   private static int HASVALS = 1;
1329   private static int HASHPHOB = 2;
1330   private static int HASDC = 3;
1331   private static int HASDESCSTR = 4;
1332   private static int HASTWOSTATE = 5; // not used yet.
1333   /**
1334    * parses the AnnotationElements - if they exist - into jalview.datamodel.Annotation[] rows
1335    * Two annotation rows are made if there are distinct annotation for both at 'pos' and 'after pos' at any particular site.
1336    * @param annotation
1337    * @return { boolean[static int constants ], int[ae.length] - map to annotated object frame, jalview.datamodel.Annotation[], jalview.datamodel.Annotation[] (after)}
1338    */
1339   private Object[] parseRangeAnnotation(uk.ac.vamsas.objects.core.RangeAnnotation
1340                                         annotation)
1341   {
1342     // set these attributes by looking in the annotation to decide what kind of alignment annotation rows will be made
1343     // TODO: potentially we might make several annotation rows from one vamsas alignment annotation. the jv2Vobj binding mechanism
1344     // may not quite cope with this (without binding an array of annotations to a vamsas alignment annotation)
1345     // summary flags saying what we found over the set of annotation rows.
1346     boolean[] AeContent = new boolean[]
1347         {
1348         false, false, false, false, false};
1349     int[] rangeMap = getMapping(annotation);
1350     jalview.datamodel.Annotation[][] anot = new jalview.datamodel.Annotation[][]
1351         {
1352         new jalview.datamodel.Annotation[rangeMap.length],
1353         new jalview.datamodel.Annotation[rangeMap.length]
1354     };
1355     boolean mergeable = true; //false  if 'after positions cant be placed on same annotation row as positions.
1356
1357     if (annotation.getAnnotationElementCount() > 0)
1358     {
1359       AnnotationElement ae[] = annotation.getAnnotationElement();
1360       for (int aa = 0; aa < ae.length; aa++)
1361       {
1362         int pos = (int) ae[aa].getPosition() - 1; // pos counts from 1 to (|seg.start-seg.end|+1)
1363         if (pos >= 0 && pos < rangeMap.length)
1364         {
1365           int row = ae[aa].getAfter() ? 1 : 0;
1366           if (anot[row][pos] != null)
1367           {
1368             // only time this should happen is if the After flag is set.
1369             Cache.log.debug("Ignoring duplicate annotation site at " + pos);
1370             continue;
1371           }
1372           if (anot[1 - row][pos] != null)
1373           {
1374             mergeable = false;
1375           }
1376           String desc = "";
1377           if (ae[aa].getDescription() != null)
1378           {
1379             desc = ae[aa].getDescription();
1380             if (desc.length() > 0)
1381             {
1382               // have imported valid description string
1383               AeContent[HASDESCSTR] = true;
1384             }
1385           }
1386           String dc = null; //ae[aa].getDisplayCharacter()==null ? "dc" : ae[aa].getDisplayCharacter();
1387           String ss = null; //ae[aa].getSecondaryStructure()==null ? "ss" : ae[aa].getSecondaryStructure();
1388           java.awt.Color colour = null;
1389           if (ae[aa].getGlyphCount() > 0)
1390           {
1391             Glyph[] glyphs = ae[aa].getGlyph();
1392             for (int g = 0; g < glyphs.length; g++)
1393             {
1394               if (glyphs[g].getDict().equals(uk.ac.vamsas.objects.utils.
1395                                              GlyphDictionary.PROTEIN_SS_3STATE))
1396               {
1397                 ss = glyphs[g].getContent();
1398                 AeContent[HASSECSTR] = true;
1399               }
1400               else if (glyphs[g].getDict().equals(uk.ac.vamsas.objects.utils.
1401                                                   GlyphDictionary.
1402                                                   PROTEIN_HD_HYDRO))
1403               {
1404                 Cache.log.debug("ignoring hydrophobicity glyph marker.");
1405                 AeContent[HASHPHOB] = true;
1406                 char c = (dc = glyphs[g].getContent()).charAt(0);
1407                 // dc may get overwritten - but we still set the colour.
1408                 colour = new java.awt.Color(c == '+' ? 255 : 0,
1409                                             c == '.' ? 255 : 0,
1410                                             c == '-' ? 255 : 0);
1411
1412               }
1413               else if (glyphs[g].getDict().equals(uk.ac.vamsas.objects.utils.
1414                                                   GlyphDictionary.DEFAULT))
1415               {
1416                 dc = glyphs[g].getContent();
1417                 AeContent[HASDC] = true;
1418               }
1419               else
1420               {
1421                 Cache.log.debug("Ignoring unknown glyph type " +
1422                                 glyphs[g].getDict());
1423               }
1424             }
1425           }
1426           float val = 0;
1427           if (ae[aa].getValueCount() > 0)
1428           {
1429             AeContent[HASVALS] = true;
1430             if (ae[aa].getValueCount() > 1)
1431             {
1432               Cache.log.warn("ignoring additional " +
1433                              (ae[aa].getValueCount() - 1) +
1434                              "values in annotation element.");
1435             }
1436             val = ae[aa].getValue(0);
1437           }
1438           if (colour == null)
1439           {
1440             anot[row][pos] = new jalview.datamodel.Annotation( (dc != null) ?
1441                 dc : "", desc, (ss != null) ? ss.charAt(0) : ' ', val);
1442           }
1443           else
1444           {
1445             anot[row][pos] = new jalview.datamodel.Annotation( (dc != null) ?
1446                 dc : "", desc, (ss != null) ? ss.charAt(0) : ' ', val, colour);
1447           }
1448         }
1449         else
1450         {
1451           Cache.log.warn("Ignoring out of bound annotation element " + aa +
1452                          " in " + annotation.getVorbaId().getId());
1453         }
1454       }
1455       // decide on how many annotation rows are needed.
1456       if (mergeable)
1457       {
1458         for (int i = 0; i < anot[0].length; i++)
1459         {
1460           if (anot[1][i] != null)
1461           {
1462             anot[0][i] = anot[1][i];
1463             anot[0][i].description = anot[0][i].description + " (after)";
1464             AeContent[HASDESCSTR] = true; // we have valid description string data
1465             anot[1][i] = null;
1466           }
1467         }
1468         anot[1] = null;
1469       }
1470       else
1471       {
1472         for (int i = 0; i < anot[0].length; i++)
1473         {
1474           anot[1][i].description = anot[1][i].description + " (after)";
1475         }
1476       }
1477       return new Object[]
1478           {
1479           AeContent, rangeMap, anot[0], anot[1]};
1480     }
1481     else
1482     {
1483       // no annotations to parse. Just return an empty annotationElement[] array.
1484       return new Object[]
1485           {
1486           AeContent, rangeMap, anot[0], anot[1]};
1487     }
1488     // return null;
1489   }
1490
1491   /**
1492    * @param jal the jalview alignment to which the annotation will be attached (ideally - freshly updated from corresponding vamsas alignment)
1493    * @param annotation
1494    * @return unbound jalview alignment annotation object.
1495    */
1496   private jalview.datamodel.AlignmentAnnotation getjAlignmentAnnotation(jalview.
1497       datamodel.AlignmentI jal,
1498       uk.ac.vamsas.objects.core.RangeAnnotation annotation)
1499   {
1500     jalview.datamodel.AlignmentAnnotation jan = null;
1501     if (annotation == null)
1502     {
1503       return null;
1504     }
1505     // boolean hasSequenceRef=annotation.getClass().equals(uk.ac.vamsas.objects.core.AlignmentSequenceAnnotation.class);
1506     //boolean hasProvenance=hasSequenceRef || (annotation.getClass().equals(uk.ac.vamsas.objects.core.AlignmentAnnotation.class));
1507     /*int se[] = getBounds(annotation);
1508          if (se==null)
1509       se=new int[] {0,jal.getWidth()-1};
1510      */
1511     Object[] parsedRangeAnnotation = parseRangeAnnotation(annotation);
1512     String a_label = annotation.getLabel();
1513     String a_descr = annotation.getDescription();
1514     if (a_label == null || a_label.length() == 0)
1515     {
1516       a_label = annotation.getType();
1517       if (a_label.length() == 0)
1518       {
1519         a_label = "Unamed annotation";
1520       }
1521     }
1522     if (a_descr == null || a_descr.length() == 0)
1523     {
1524       a_descr = "Annotation of type '" + annotation.getType() + "'";
1525     }
1526     if (parsedRangeAnnotation == null)
1527     {
1528       Cache.log.debug(
1529           "Inserting empty annotation row elements for a whole-alignment annotation.");
1530
1531     }
1532     else
1533     {
1534       if (parsedRangeAnnotation[3] != null)
1535       {
1536         Cache.log.warn("Ignoring 'After' annotation row in " +
1537                        annotation.getVorbaId());
1538       }
1539       jalview.datamodel.Annotation[] arow = (jalview.datamodel.Annotation[])
1540           parsedRangeAnnotation[2];
1541       boolean[] has = (boolean[]) parsedRangeAnnotation[0];
1542       // VAMSAS: getGraph is only on derived annotation for alignments - in this way its 'odd' - there is already an existing TODO about removing this flag as being redundant
1543       /*if ((annotation.getClass().equals(uk.ac.vamsas.objects.core.AlignmentAnnotation.class) && ((uk.ac.vamsas.objects.core.AlignmentAnnotation)annotation).getGraph())
1544           || (hasSequenceRef=true && ((uk.ac.vamsas.objects.core.AlignmentSequenceAnnotation)annotation).getGraph())) {
1545        */
1546       if (has[HASVALS])
1547       {
1548         // make bounds and automatic description strings for jalview user's benefit (these shouldn't be written back to vamsas document)
1549         boolean first = true;
1550         float min = 0, max = 1;
1551         int lastval = 0;
1552         for (int i = 0; i < arow.length; i++)
1553         {
1554           if (arow[i] != null)
1555           {
1556             if (i - lastval > 1)
1557             {
1558               // do some interpolation *between* points
1559               if (arow[lastval] != null)
1560               {
1561                 float interval = arow[i].value - arow[lastval].value;
1562                 interval /= i - lastval;
1563                 float base = arow[lastval].value;
1564                 for (int ip = lastval + 1, np = 0; ip < i; np++, ip++)
1565                 {
1566                   arow[ip] = new jalview.datamodel.Annotation("", "", ' ',
1567                       interval * np + base);
1568                   // NB - Interpolated points don't get a tooltip and description.
1569                 }
1570               }
1571             }
1572             lastval = i;
1573             // check range - shouldn't we have a min and max property in the annotation object ?
1574             if (first)
1575             {
1576               min = max = arow[i].value;
1577               first = false;
1578             }
1579             else
1580             {
1581               if (arow[i].value < min)
1582               {
1583                 min = arow[i].value;
1584               }
1585               else if (arow[i].value > max)
1586               {
1587                 max = arow[i].value;
1588               }
1589             }
1590             // make tooltip and display char value
1591             if (!has[HASDESCSTR])
1592             {
1593               arow[i].description = arow[i].value + "";
1594             }
1595             if (!has[HASDC])
1596             {
1597               arow[i].displayCharacter = arow[i].value + "";
1598             }
1599           }
1600         }
1601         int type = jalview.datamodel.AlignmentAnnotation.LINE_GRAPH;
1602         if (has[HASHPHOB])
1603         {
1604           type = jalview.datamodel.AlignmentAnnotation.BAR_GRAPH;
1605         }
1606         jan = new jalview.datamodel.AlignmentAnnotation(a_label, a_descr, arow,
1607             min, max, type);
1608       }
1609       else
1610       {
1611         jan = new jalview.datamodel.AlignmentAnnotation(a_label, a_descr, arow);
1612         jan.setThreshold(null);
1613       }
1614       if (annotation.getLinkCount() > 0)
1615       {
1616         Cache.log.warn("Ignoring " + annotation.getLinkCount() +
1617                        "links added to AlignmentAnnotation.");
1618       }
1619       if (annotation.getModifiable()==null) // TODO: USE VAMSAS LIBRARY OBJECT LOCK METHODS)
1620       {
1621         jan.editable = true;
1622       }
1623
1624       if (annotation.getPropertyCount() > 0)
1625       {
1626         // look for special jalview properties
1627         uk.ac.vamsas.objects.core.Property[] props = annotation.getProperty();
1628         for (int p = 0; p < props.length; p++)
1629         {
1630           if (props[p].getName().equalsIgnoreCase("jalview:graphType"))
1631           {
1632             try
1633             {
1634               // probably a jalview annotation graph so recover the visualization hints.
1635               jan.graph = jalview.datamodel.AlignmentAnnotation.
1636                   getGraphValueFromString(props[p].getContent());
1637             }
1638             catch (Exception e)
1639             {
1640               Cache.log.debug(
1641                   "Invalid graph type value in jalview:graphType property.");
1642             }
1643             try
1644             {
1645               if (annotation.getGroup() != null &&
1646                   annotation.getGroup().length() > 0)
1647               {
1648                 jan.graphGroup = Integer.parseInt(annotation.getGroup());
1649               }
1650             }
1651             catch (Exception e)
1652             {
1653               Cache.log.info("UNIMPLEMENTED : Couldn't parse non-integer group value for setting graphGroup correctly.");
1654             }
1655           }
1656         }
1657       }
1658
1659       return jan;
1660
1661     }
1662
1663     return null;
1664   }
1665
1666   private SequenceFeature getJalviewSeqFeature(RangeAnnotation dseta)
1667   {
1668     int[] se = getBounds(dseta);
1669     SequenceFeature sf = new jalview.datamodel.SequenceFeature(dseta.getType(),
1670         dseta.getDescription(), dseta.getStatus(), se[0], se[1], dseta
1671         .getGroup());
1672     if (dseta.getLinkCount() > 0)
1673     {
1674       Link[] links = dseta.getLink();
1675       for (int i = 0; i < links.length; i++)
1676       {
1677         sf.addLink(links[i].getContent() + "|" + links[i].getHref());
1678       }
1679     }
1680     return sf;
1681   }
1682
1683   /**
1684    * get real bounds of a RangeType's specification. start and end are an
1685    * inclusive range within which all segments and positions lie.
1686    * TODO: refactor to vamsas utils
1687    * @param dseta
1688    * @return int[] { start, end}
1689    */
1690   private int[] getBounds(RangeType dseta)
1691   {
1692     if (dseta != null)
1693     {
1694       int[] se = null;
1695       if (dseta.getSegCount() > 0 && dseta.getPosCount() > 0)
1696       {
1697         throw new Error("Invalid vamsas RangeType - cannot resolve both lists of Pos and Seg from choice!");
1698       }
1699       if (dseta.getSegCount() > 0)
1700       {
1701         se = getSegRange(dseta.getSeg(0), true);
1702         for (int s = 1, sSize = dseta.getSegCount(); s < sSize; s++)
1703         {
1704           int nse[] = getSegRange(dseta.getSeg(s), true);
1705           if (se[0] > nse[0])
1706           {
1707             se[0] = nse[0];
1708           }
1709           if (se[1] < nse[1])
1710           {
1711             se[1] = nse[1];
1712           }
1713         }
1714       }
1715       if (dseta.getPosCount() > 0)
1716       {
1717         // could do a polarity for pos range too. and pass back indication of discontinuities.
1718         int pos = dseta.getPos(0).getI();
1719         se = new int[]
1720             {
1721             pos, pos};
1722         for (int p = 0, pSize = dseta.getPosCount(); p < pSize; p++)
1723         {
1724           pos = dseta.getPos(p).getI();
1725           if (se[0] > pos)
1726           {
1727             se[0] = pos;
1728           }
1729           if (se[1] < pos)
1730           {
1731             se[1] = pos;
1732           }
1733         }
1734       }
1735       return se;
1736     }
1737     return null;
1738   }
1739
1740   /**
1741    * map from a rangeType's internal frame to the referenced object's coordinate frame.
1742    * @param dseta
1743    * @return int [] { ref(pos)...} for all pos in rangeType's frame.
1744    */
1745   private int[] getMapping(RangeType dseta)
1746   {
1747     Vector posList = new Vector();
1748     if (dseta != null)
1749     {
1750       int[] se = null;
1751       if (dseta.getSegCount() > 0 && dseta.getPosCount() > 0)
1752       {
1753         throw new Error("Invalid vamsas RangeType - cannot resolve both lists of Pos and Seg from choice!");
1754       }
1755       if (dseta.getSegCount() > 0)
1756       {
1757         for (int s = 0, sSize = dseta.getSegCount(); s < sSize; s++)
1758         {
1759           se = getSegRange(dseta.getSeg(s), false);
1760           int se_end = se[1 - se[2]] + (se[2] == 0 ? 1 : -1);
1761           for (int p = se[se[2]]; p != se_end; p += se[2] == 0 ? 1 : -1)
1762           {
1763             posList.add(new Integer(p));
1764           }
1765         }
1766       }
1767       else if (dseta.getPosCount() > 0)
1768       {
1769         int pos = dseta.getPos(0).getI();
1770
1771         for (int p = 0, pSize = dseta.getPosCount(); p < pSize; p++)
1772         {
1773           pos = dseta.getPos(p).getI();
1774           posList.add(new Integer(pos));
1775         }
1776       }
1777     }
1778     if (posList != null && posList.size() > 0)
1779     {
1780       int[] range = new int[posList.size()];
1781       for (int i = 0; i < range.length; i++)
1782       {
1783         range[i] = ( (Integer) posList.elementAt(i)).intValue();
1784       }
1785       posList.clear();
1786       return range;
1787     }
1788     return null;
1789   }
1790
1791   /* not needed now.
1792    * Provenance getVamsasProvenance(jalview.datamodel.Provenance jprov) {
1793     jalview.datamodel.ProvenanceEntry[] entries = null;
1794     // TODO: fix App and Action here.
1795     Provenance prov = new Provenance();
1796     org.exolab.castor.types.Date date = new org.exolab.castor.types.Date(
1797         new java.util.Date());
1798     Entry provEntry;
1799
1800     if (jprov != null)
1801     {
1802       entries = jprov.getEntries();
1803       for (int i = 0; i < entries.length; i++)
1804       {
1805         provEntry = new Entry();
1806         try
1807         {
1808           date = new org.exolab.castor.types.Date(entries[i].getDate());
1809         } catch (Exception ex)
1810         {
1811           ex.printStackTrace();
1812
1813           date = new org.exolab.castor.types.Date(entries[i].getDate());
1814         }
1815         provEntry.setDate(date);
1816         provEntry.setUser(entries[i].getUser());
1817         provEntry.setAction(entries[i].getAction());
1818         prov.addEntry(provEntry);
1819       }
1820     }
1821     else
1822     {
1823       provEntry = new Entry();
1824       provEntry.setDate(date);
1825       provEntry.setUser(System.getProperty("user.name")); // TODO: ext string
1826       provEntry.setApp("JVAPP"); // TODO: ext string
1827       provEntry.setAction(action);
1828       prov.addEntry(provEntry);
1829     }
1830
1831     return prov;
1832      }
1833    */
1834   jalview.datamodel.Provenance getJalviewProvenance(Provenance prov)
1835   {
1836     // TODO: fix App and Action entries and check use of provenance in jalview.
1837     jalview.datamodel.Provenance jprov = new jalview.datamodel.Provenance();
1838     for (int i = 0; i < prov.getEntryCount(); i++)
1839     {
1840       jprov.addEntry(prov.getEntry(i).getUser(), prov.getEntry(i).getAction(),
1841                      prov.getEntry(i).getDate().toDate(),
1842                      prov.getEntry(i).getId());
1843     }
1844
1845     return jprov;
1846   }
1847
1848   /**
1849    *
1850    * @return default initial provenance list for a Jalview created vamsas
1851    *         object.
1852    */
1853   Provenance dummyProvenance()
1854   {
1855     return dummyProvenance(null);
1856   }
1857
1858   Entry dummyPEntry(String action)
1859   {
1860     Entry entry = new Entry();
1861     entry.setApp(this.provEntry.getApp());
1862     if (action != null)
1863     {
1864       entry.setAction(action);
1865     }
1866     else
1867     {
1868       entry.setAction("created.");
1869     }
1870     entry.setDate(new org.exolab.castor.types.Date(new java.util.Date()));
1871     entry.setUser(this.provEntry.getUser());
1872     return entry;
1873   }
1874
1875   Provenance dummyProvenance(String action)
1876   {
1877     Provenance prov = new Provenance();
1878     prov.addEntry(dummyPEntry(action));
1879     return prov;
1880   }
1881
1882   void addProvenance(Provenance p, String action)
1883   {
1884     p.addEntry(dummyPEntry(action));
1885   }
1886
1887 }