JAL-2023 CDS sequences added to / share alignment dataset
[jalview.git] / src / jalview / gui / Jalview2XML.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.gui;
22
23 import jalview.api.ViewStyleI;
24 import jalview.api.structures.JalviewStructureDisplayI;
25 import jalview.bin.Cache;
26 import jalview.datamodel.AlignedCodonFrame;
27 import jalview.datamodel.Alignment;
28 import jalview.datamodel.AlignmentAnnotation;
29 import jalview.datamodel.AlignmentI;
30 import jalview.datamodel.PDBEntry;
31 import jalview.datamodel.RnaViewerModel;
32 import jalview.datamodel.SequenceGroup;
33 import jalview.datamodel.SequenceI;
34 import jalview.datamodel.StructureViewerModel;
35 import jalview.datamodel.StructureViewerModel.StructureData;
36 import jalview.ext.varna.RnaModel;
37 import jalview.gui.StructureViewer.ViewerType;
38 import jalview.schemabinding.version2.AlcodMap;
39 import jalview.schemabinding.version2.AlcodonFrame;
40 import jalview.schemabinding.version2.Annotation;
41 import jalview.schemabinding.version2.AnnotationColours;
42 import jalview.schemabinding.version2.AnnotationElement;
43 import jalview.schemabinding.version2.CalcIdParam;
44 import jalview.schemabinding.version2.DBRef;
45 import jalview.schemabinding.version2.Features;
46 import jalview.schemabinding.version2.Group;
47 import jalview.schemabinding.version2.HiddenColumns;
48 import jalview.schemabinding.version2.JGroup;
49 import jalview.schemabinding.version2.JSeq;
50 import jalview.schemabinding.version2.JalviewModel;
51 import jalview.schemabinding.version2.JalviewModelSequence;
52 import jalview.schemabinding.version2.MapListFrom;
53 import jalview.schemabinding.version2.MapListTo;
54 import jalview.schemabinding.version2.Mapping;
55 import jalview.schemabinding.version2.MappingChoice;
56 import jalview.schemabinding.version2.OtherData;
57 import jalview.schemabinding.version2.PdbentryItem;
58 import jalview.schemabinding.version2.Pdbids;
59 import jalview.schemabinding.version2.Property;
60 import jalview.schemabinding.version2.RnaViewer;
61 import jalview.schemabinding.version2.SecondaryStructure;
62 import jalview.schemabinding.version2.Sequence;
63 import jalview.schemabinding.version2.SequenceSet;
64 import jalview.schemabinding.version2.SequenceSetProperties;
65 import jalview.schemabinding.version2.Setting;
66 import jalview.schemabinding.version2.StructureState;
67 import jalview.schemabinding.version2.ThresholdLine;
68 import jalview.schemabinding.version2.Tree;
69 import jalview.schemabinding.version2.UserColours;
70 import jalview.schemabinding.version2.Viewport;
71 import jalview.schemes.AnnotationColourGradient;
72 import jalview.schemes.ColourSchemeI;
73 import jalview.schemes.ColourSchemeProperty;
74 import jalview.schemes.GraduatedColor;
75 import jalview.schemes.ResidueColourScheme;
76 import jalview.schemes.ResidueProperties;
77 import jalview.schemes.UserColourScheme;
78 import jalview.structure.StructureSelectionManager;
79 import jalview.structures.models.AAStructureBindingModel;
80 import jalview.util.MessageManager;
81 import jalview.util.Platform;
82 import jalview.util.jarInputStreamProvider;
83 import jalview.viewmodel.AlignmentViewport;
84 import jalview.viewmodel.seqfeatures.FeatureRendererSettings;
85 import jalview.viewmodel.seqfeatures.FeaturesDisplayed;
86 import jalview.ws.jws2.Jws2Discoverer;
87 import jalview.ws.jws2.dm.AAConSettings;
88 import jalview.ws.jws2.jabaws2.Jws2Instance;
89 import jalview.ws.params.ArgumentI;
90 import jalview.ws.params.AutoCalcSetting;
91 import jalview.ws.params.WsParamSetI;
92
93 import java.awt.Rectangle;
94 import java.io.BufferedReader;
95 import java.io.DataInputStream;
96 import java.io.DataOutputStream;
97 import java.io.File;
98 import java.io.FileInputStream;
99 import java.io.FileOutputStream;
100 import java.io.IOException;
101 import java.io.InputStreamReader;
102 import java.io.OutputStreamWriter;
103 import java.io.PrintWriter;
104 import java.lang.reflect.InvocationTargetException;
105 import java.net.MalformedURLException;
106 import java.net.URL;
107 import java.util.ArrayList;
108 import java.util.Enumeration;
109 import java.util.HashMap;
110 import java.util.HashSet;
111 import java.util.Hashtable;
112 import java.util.IdentityHashMap;
113 import java.util.Iterator;
114 import java.util.LinkedHashMap;
115 import java.util.List;
116 import java.util.Map;
117 import java.util.Map.Entry;
118 import java.util.Set;
119 import java.util.StringTokenizer;
120 import java.util.Vector;
121 import java.util.jar.JarEntry;
122 import java.util.jar.JarInputStream;
123 import java.util.jar.JarOutputStream;
124
125 import javax.swing.JInternalFrame;
126 import javax.swing.JOptionPane;
127 import javax.swing.SwingUtilities;
128
129 import org.exolab.castor.xml.Marshaller;
130 import org.exolab.castor.xml.Unmarshaller;
131
132 /**
133  * Write out the current jalview desktop state as a Jalview XML stream.
134  * 
135  * Note: the vamsas objects referred to here are primitive versions of the
136  * VAMSAS project schema elements - they are not the same and most likely never
137  * will be :)
138  * 
139  * @author $author$
140  * @version $Revision: 1.134 $
141  */
142 public class Jalview2XML
143 {
144   private static final String VIEWER_PREFIX = "viewer_";
145
146   private static final String RNA_PREFIX = "rna_";
147
148   private static final String UTF_8 = "UTF-8";
149
150   // use this with nextCounter() to make unique names for entities
151   private int counter = 0;
152
153   /*
154    * SequenceI reference -> XML ID string in jalview XML. Populated as XML reps
155    * of sequence objects are created.
156    */
157   IdentityHashMap<SequenceI, String> seqsToIds = null;
158
159   /**
160    * jalview XML Sequence ID to jalview sequence object reference (both dataset
161    * and alignment sequences. Populated as XML reps of sequence objects are
162    * created.)
163    */
164   Map<String, SequenceI> seqRefIds = null;
165
166   Vector<Object[]> frefedSequence = null;
167
168   boolean raiseGUI = true; // whether errors are raised in dialog boxes or not
169
170   /*
171    * Map of reconstructed AlignFrame objects that appear to have come from
172    * SplitFrame objects (have a dna/protein complement view).
173    */
174   private Map<Viewport, AlignFrame> splitFrameCandidates = new HashMap<Viewport, AlignFrame>();
175
176   /*
177    * Map from displayed rna structure models to their saved session state jar
178    * entry names
179    */
180   private Map<RnaModel, String> rnaSessions = new HashMap<RnaModel, String>();
181
182   /**
183    * create/return unique hash string for sq
184    * 
185    * @param sq
186    * @return new or existing unique string for sq
187    */
188   String seqHash(SequenceI sq)
189   {
190     if (seqsToIds == null)
191     {
192       initSeqRefs();
193     }
194     if (seqsToIds.containsKey(sq))
195     {
196       return seqsToIds.get(sq);
197     }
198     else
199     {
200       // create sequential key
201       String key = "sq" + (seqsToIds.size() + 1);
202       key = makeHashCode(sq, key); // check we don't have an external reference
203       // for it already.
204       seqsToIds.put(sq, key);
205       return key;
206     }
207   }
208
209   void clearSeqRefs()
210   {
211     if (_cleartables)
212     {
213       if (seqRefIds != null)
214       {
215         seqRefIds.clear();
216       }
217       if (seqsToIds != null)
218       {
219         seqsToIds.clear();
220       }
221       // seqRefIds = null;
222       // seqsToIds = null;
223     }
224     else
225     {
226       // do nothing
227       warn("clearSeqRefs called when _cleartables was not set. Doing nothing.");
228       // seqRefIds = new Hashtable();
229       // seqsToIds = new IdentityHashMap();
230     }
231   }
232
233   void initSeqRefs()
234   {
235     if (seqsToIds == null)
236     {
237       seqsToIds = new IdentityHashMap<SequenceI, String>();
238     }
239     if (seqRefIds == null)
240     {
241       seqRefIds = new HashMap<String, SequenceI>();
242     }
243   }
244
245   public Jalview2XML()
246   {
247   }
248
249   public Jalview2XML(boolean raiseGUI)
250   {
251     this.raiseGUI = raiseGUI;
252   }
253
254   public void resolveFrefedSequences()
255   {
256     if (frefedSequence.size() > 0)
257     {
258       int r = 0, rSize = frefedSequence.size();
259       while (r < rSize)
260       {
261         Object[] ref = frefedSequence.elementAt(r);
262         if (ref != null)
263         {
264           String sref = (String) ref[0];
265           if (seqRefIds.containsKey(sref))
266           {
267             if (ref[1] instanceof jalview.datamodel.Mapping)
268             {
269               SequenceI seq = seqRefIds.get(sref);
270               while (seq.getDatasetSequence() != null)
271               {
272                 seq = seq.getDatasetSequence();
273               }
274               ((jalview.datamodel.Mapping) ref[1]).setTo(seq);
275             }
276             else
277             {
278               if (ref[1] instanceof jalview.datamodel.AlignedCodonFrame)
279               {
280                 SequenceI seq = seqRefIds.get(sref);
281                 while (seq.getDatasetSequence() != null)
282                 {
283                   seq = seq.getDatasetSequence();
284                 }
285                 if (ref[2] != null
286                         && ref[2] instanceof jalview.datamodel.Mapping)
287                 {
288                   jalview.datamodel.Mapping mp = (jalview.datamodel.Mapping) ref[2];
289                   ((jalview.datamodel.AlignedCodonFrame) ref[1]).addMap(
290                           seq, mp.getTo(), mp.getMap());
291                 }
292                 else
293                 {
294                   System.err
295                           .println("IMPLEMENTATION ERROR: Unimplemented forward sequence references for AlcodonFrames involving "
296                                   + ref[2].getClass() + " type objects.");
297                 }
298               }
299               else
300               {
301                 System.err
302                         .println("IMPLEMENTATION ERROR: Unimplemented forward sequence references for "
303                                 + ref[1].getClass() + " type objects.");
304               }
305             }
306             frefedSequence.remove(r);
307             rSize--;
308           }
309           else
310           {
311             System.err
312                     .println("IMPLEMENTATION WARNING: Unresolved forward reference for hash string "
313                             + ref[0]
314                             + " with objecttype "
315                             + ref[1].getClass());
316             r++;
317           }
318         }
319         else
320         {
321           // empty reference
322           frefedSequence.remove(r);
323           rSize--;
324         }
325       }
326     }
327   }
328
329   /**
330    * This maintains a map of viewports, the key being the seqSetId. Important to
331    * set historyItem and redoList for multiple views
332    */
333   Map<String, AlignViewport> viewportsAdded = new HashMap<String, AlignViewport>();
334
335   Map<String, AlignmentAnnotation> annotationIds = new HashMap<String, AlignmentAnnotation>();
336
337   String uniqueSetSuffix = "";
338
339   /**
340    * List of pdbfiles added to Jar
341    */
342   List<String> pdbfiles = null;
343
344   // SAVES SEVERAL ALIGNMENT WINDOWS TO SAME JARFILE
345   public void saveState(File statefile)
346   {
347     FileOutputStream fos = null;
348     try
349     {
350       fos = new FileOutputStream(statefile);
351       JarOutputStream jout = new JarOutputStream(fos);
352       saveState(jout);
353
354     } catch (Exception e)
355     {
356       // TODO: inform user of the problem - they need to know if their data was
357       // not saved !
358       if (errorMessage == null)
359       {
360         errorMessage = "Couldn't write Jalview Archive to output file '"
361                 + statefile + "' - See console error log for details";
362       }
363       else
364       {
365         errorMessage += "(output file was '" + statefile + "')";
366       }
367       e.printStackTrace();
368     } finally
369     {
370       if (fos != null)
371       {
372         try
373         {
374           fos.close();
375         } catch (IOException e)
376         {
377           // ignore
378         }
379       }
380     }
381     reportErrors();
382   }
383
384   /**
385    * Writes a jalview project archive to the given Jar output stream.
386    * 
387    * @param jout
388    */
389   public void saveState(JarOutputStream jout)
390   {
391     AlignFrame[] frames = Desktop.getAlignFrames();
392
393     if (frames == null)
394     {
395       return;
396     }
397
398     Hashtable<String, AlignFrame> dsses = new Hashtable<String, AlignFrame>();
399
400     /*
401      * ensure cached data is clear before starting
402      */
403     // todo tidy up seqRefIds, seqsToIds initialisation / reset
404     rnaSessions.clear();
405     splitFrameCandidates.clear();
406
407     try
408     {
409
410       // NOTE UTF-8 MUST BE USED FOR WRITING UNICODE CHARS
411       // //////////////////////////////////////////////////
412
413       List<String> shortNames = new ArrayList<String>();
414       List<String> viewIds = new ArrayList<String>();
415
416       // REVERSE ORDER
417       for (int i = frames.length - 1; i > -1; i--)
418       {
419         AlignFrame af = frames[i];
420         // skip ?
421         if (skipList != null
422                 && skipList
423                         .containsKey(af.getViewport().getSequenceSetId()))
424         {
425           continue;
426         }
427
428         String shortName = makeFilename(af, shortNames);
429
430         int ap, apSize = af.alignPanels.size();
431
432         for (ap = 0; ap < apSize; ap++)
433         {
434           AlignmentPanel apanel = af.alignPanels.get(ap);
435           String fileName = apSize == 1 ? shortName : ap + shortName;
436           if (!fileName.endsWith(".xml"))
437           {
438             fileName = fileName + ".xml";
439           }
440
441           saveState(apanel, fileName, jout, viewIds);
442
443           String dssid = getDatasetIdRef(af.getViewport().getAlignment()
444                   .getDataset());
445           if (!dsses.containsKey(dssid))
446           {
447             dsses.put(dssid, af);
448           }
449         }
450       }
451
452       writeDatasetFor(dsses, "" + jout.hashCode() + " " + uniqueSetSuffix,
453               jout);
454
455       try
456       {
457         jout.flush();
458       } catch (Exception foo)
459       {
460       }
461       ;
462       jout.close();
463     } catch (Exception ex)
464     {
465       // TODO: inform user of the problem - they need to know if their data was
466       // not saved !
467       if (errorMessage == null)
468       {
469         errorMessage = "Couldn't write Jalview Archive - see error output for details";
470       }
471       ex.printStackTrace();
472     }
473   }
474
475   /**
476    * Generates a distinct file name, based on the title of the AlignFrame, by
477    * appending _n for increasing n until an unused name is generated. The new
478    * name (without its extension) is added to the list.
479    * 
480    * @param af
481    * @param namesUsed
482    * @return the generated name, with .xml extension
483    */
484   protected String makeFilename(AlignFrame af, List<String> namesUsed)
485   {
486     String shortName = af.getTitle();
487
488     if (shortName.indexOf(File.separatorChar) > -1)
489     {
490       shortName = shortName.substring(shortName
491               .lastIndexOf(File.separatorChar) + 1);
492     }
493
494     int count = 1;
495
496     while (namesUsed.contains(shortName))
497     {
498       if (shortName.endsWith("_" + (count - 1)))
499       {
500         shortName = shortName.substring(0, shortName.lastIndexOf("_"));
501       }
502
503       shortName = shortName.concat("_" + count);
504       count++;
505     }
506
507     namesUsed.add(shortName);
508
509     if (!shortName.endsWith(".xml"))
510     {
511       shortName = shortName + ".xml";
512     }
513     return shortName;
514   }
515
516   // USE THIS METHOD TO SAVE A SINGLE ALIGNMENT WINDOW
517   public boolean saveAlignment(AlignFrame af, String jarFile,
518           String fileName)
519   {
520     try
521     {
522       int ap = 0;
523       int apSize = af.alignPanels.size();
524       FileOutputStream fos = new FileOutputStream(jarFile);
525       JarOutputStream jout = new JarOutputStream(fos);
526       Hashtable<String, AlignFrame> dsses = new Hashtable<String, AlignFrame>();
527       List<String> viewIds = new ArrayList<String>();
528
529       for (AlignmentPanel apanel : af.alignPanels)
530       {
531         String jfileName = apSize == 1 ? fileName : fileName + ap;
532         ap++;
533         if (!jfileName.endsWith(".xml"))
534         {
535           jfileName = jfileName + ".xml";
536         }
537         saveState(apanel, jfileName, jout, viewIds);
538         String dssid = getDatasetIdRef(af.getViewport().getAlignment()
539                 .getDataset());
540         if (!dsses.containsKey(dssid))
541         {
542           dsses.put(dssid, af);
543         }
544       }
545       writeDatasetFor(dsses, fileName, jout);
546       try
547       {
548         jout.flush();
549       } catch (Exception foo)
550       {
551       }
552       ;
553       jout.close();
554       return true;
555     } catch (Exception ex)
556     {
557       errorMessage = "Couldn't Write alignment view to Jalview Archive - see error output for details";
558       ex.printStackTrace();
559       return false;
560     }
561   }
562
563   private void writeDatasetFor(Hashtable<String, AlignFrame> dsses,
564           String fileName, JarOutputStream jout)
565   {
566
567     for (String dssids : dsses.keySet())
568     {
569       AlignFrame _af = dsses.get(dssids);
570       String jfileName = fileName + " Dataset for " + _af.getTitle();
571       if (!jfileName.endsWith(".xml"))
572       {
573         jfileName = jfileName + ".xml";
574       }
575       saveState(_af.alignPanel, jfileName, true, jout, null);
576     }
577   }
578
579   /**
580    * create a JalviewModel from an alignment view and marshall it to a
581    * JarOutputStream
582    * 
583    * @param ap
584    *          panel to create jalview model for
585    * @param fileName
586    *          name of alignment panel written to output stream
587    * @param jout
588    *          jar output stream
589    * @param viewIds
590    * @param out
591    *          jar entry name
592    */
593   public JalviewModel saveState(AlignmentPanel ap, String fileName,
594           JarOutputStream jout, List<String> viewIds)
595   {
596     return saveState(ap, fileName, false, jout, viewIds);
597   }
598
599   /**
600    * create a JalviewModel from an alignment view and marshall it to a
601    * JarOutputStream
602    * 
603    * @param ap
604    *          panel to create jalview model for
605    * @param fileName
606    *          name of alignment panel written to output stream
607    * @param storeDS
608    *          when true, only write the dataset for the alignment, not the data
609    *          associated with the view.
610    * @param jout
611    *          jar output stream
612    * @param out
613    *          jar entry name
614    */
615   public JalviewModel saveState(AlignmentPanel ap, String fileName,
616           boolean storeDS, JarOutputStream jout, List<String> viewIds)
617   {
618     if (viewIds == null)
619     {
620       viewIds = new ArrayList<String>();
621     }
622
623     initSeqRefs();
624
625     List<UserColourScheme> userColours = new ArrayList<UserColourScheme>();
626
627     AlignViewport av = ap.av;
628
629     JalviewModel object = new JalviewModel();
630     object.setVamsasModel(new jalview.schemabinding.version2.VamsasModel());
631
632     object.setCreationDate(new java.util.Date(System.currentTimeMillis()));
633     object.setVersion(jalview.bin.Cache.getDefault("VERSION",
634             "Development Build"));
635
636     jalview.datamodel.AlignmentI jal = av.getAlignment();
637
638     if (av.hasHiddenRows())
639     {
640       jal = jal.getHiddenSequences().getFullAlignment();
641     }
642
643     SequenceSet vamsasSet = new SequenceSet();
644     Sequence vamsasSeq;
645     JalviewModelSequence jms = new JalviewModelSequence();
646
647     vamsasSet.setGapChar(jal.getGapCharacter() + "");
648
649     if (jal.getDataset() != null)
650     {
651       // dataset id is the dataset's hashcode
652       vamsasSet.setDatasetId(getDatasetIdRef(jal.getDataset()));
653       if (storeDS)
654       {
655         // switch jal and the dataset
656         jal = jal.getDataset();
657       }
658     }
659     if (jal.getProperties() != null)
660     {
661       Enumeration en = jal.getProperties().keys();
662       while (en.hasMoreElements())
663       {
664         String key = en.nextElement().toString();
665         SequenceSetProperties ssp = new SequenceSetProperties();
666         ssp.setKey(key);
667         ssp.setValue(jal.getProperties().get(key).toString());
668         vamsasSet.addSequenceSetProperties(ssp);
669       }
670     }
671
672     JSeq jseq;
673     Set<String> calcIdSet = new HashSet<String>();
674
675     // SAVE SEQUENCES
676     for (int i = 0; i < jal.getHeight(); i++)
677     {
678       final SequenceI jds = jal.getSequenceAt(i);
679       final SequenceI jdatasq = jds.getDatasetSequence() == null ? jds
680               : jds.getDatasetSequence();
681       String id = seqHash(jds);
682
683       if (seqRefIds.get(id) != null)
684       {
685         // This happens for two reasons: 1. multiple views are being serialised.
686         // 2. the hashCode has collided with another sequence's code. This DOES
687         // HAPPEN! (PF00072.15.stk does this)
688         // JBPNote: Uncomment to debug writing out of files that do not read
689         // back in due to ArrayOutOfBoundExceptions.
690         // System.err.println("vamsasSeq backref: "+id+"");
691         // System.err.println(jds.getName()+"
692         // "+jds.getStart()+"-"+jds.getEnd()+" "+jds.getSequenceAsString());
693         // System.err.println("Hashcode: "+seqHash(jds));
694         // SequenceI rsq = (SequenceI) seqRefIds.get(id + "");
695         // System.err.println(rsq.getName()+"
696         // "+rsq.getStart()+"-"+rsq.getEnd()+" "+rsq.getSequenceAsString());
697         // System.err.println("Hashcode: "+seqHash(rsq));
698       }
699       else
700       {
701         vamsasSeq = createVamsasSequence(id, jds);
702         vamsasSet.addSequence(vamsasSeq);
703         seqRefIds.put(id, jds);
704       }
705
706       jseq = new JSeq();
707       jseq.setStart(jds.getStart());
708       jseq.setEnd(jds.getEnd());
709       jseq.setColour(av.getSequenceColour(jds).getRGB());
710
711       jseq.setId(id); // jseq id should be a string not a number
712       if (!storeDS)
713       {
714         // Store any sequences this sequence represents
715         if (av.hasHiddenRows())
716         {
717           jseq.setHidden(av.getAlignment().getHiddenSequences()
718                   .isHidden(jds));
719
720           if (av.isHiddenRepSequence(jal.getSequenceAt(i)))
721           {
722             jalview.datamodel.SequenceI[] reps = av
723                     .getRepresentedSequences(jal.getSequenceAt(i))
724                     .getSequencesInOrder(jal);
725
726             for (int h = 0; h < reps.length; h++)
727             {
728               if (reps[h] != jal.getSequenceAt(i))
729               {
730                 jseq.addHiddenSequences(jal.findIndex(reps[h]));
731               }
732             }
733           }
734         }
735       }
736
737       if (jds.getSequenceFeatures() != null)
738       {
739         jalview.datamodel.SequenceFeature[] sf = jds.getSequenceFeatures();
740         int index = 0;
741         while (index < sf.length)
742         {
743           Features features = new Features();
744
745           features.setBegin(sf[index].getBegin());
746           features.setEnd(sf[index].getEnd());
747           features.setDescription(sf[index].getDescription());
748           features.setType(sf[index].getType());
749           features.setFeatureGroup(sf[index].getFeatureGroup());
750           features.setScore(sf[index].getScore());
751           if (sf[index].links != null)
752           {
753             for (int l = 0; l < sf[index].links.size(); l++)
754             {
755               OtherData keyValue = new OtherData();
756               keyValue.setKey("LINK_" + l);
757               keyValue.setValue(sf[index].links.elementAt(l).toString());
758               features.addOtherData(keyValue);
759             }
760           }
761           if (sf[index].otherDetails != null)
762           {
763             String key;
764             Iterator<String> keys = sf[index].otherDetails.keySet()
765                     .iterator();
766             while (keys.hasNext())
767             {
768               key = keys.next();
769               OtherData keyValue = new OtherData();
770               keyValue.setKey(key);
771               keyValue.setValue(sf[index].otherDetails.get(key).toString());
772               features.addOtherData(keyValue);
773             }
774           }
775
776           jseq.addFeatures(features);
777           index++;
778         }
779       }
780
781       if (jdatasq.getAllPDBEntries() != null)
782       {
783         Enumeration en = jdatasq.getAllPDBEntries().elements();
784         while (en.hasMoreElements())
785         {
786           Pdbids pdb = new Pdbids();
787           jalview.datamodel.PDBEntry entry = (jalview.datamodel.PDBEntry) en
788                   .nextElement();
789
790           String pdbId = entry.getId();
791           pdb.setId(pdbId);
792           pdb.setType(entry.getType());
793
794           /*
795            * Store any structure views associated with this sequence. This
796            * section copes with duplicate entries in the project, so a dataset
797            * only view *should* be coped with sensibly.
798            */
799           // This must have been loaded, is it still visible?
800           JInternalFrame[] frames = Desktop.desktop.getAllFrames();
801           String matchedFile = null;
802           for (int f = frames.length - 1; f > -1; f--)
803           {
804             if (frames[f] instanceof StructureViewerBase)
805             {
806               StructureViewerBase viewFrame = (StructureViewerBase) frames[f];
807               matchedFile = saveStructureState(ap, jds, pdb, entry,
808                       viewIds, matchedFile, viewFrame);
809               /*
810                * Only store each structure viewer's state once in the project
811                * jar. First time through only (storeDS==false)
812                */
813               String viewId = viewFrame.getViewId();
814               if (!storeDS && !viewIds.contains(viewId))
815               {
816                 viewIds.add(viewId);
817                 try
818                 {
819                   String viewerState = viewFrame.getStateInfo();
820                   writeJarEntry(jout, getViewerJarEntryName(viewId),
821                           viewerState.getBytes());
822                 } catch (IOException e)
823                 {
824                   System.err.println("Error saving viewer state: "
825                           + e.getMessage());
826                 }
827               }
828             }
829           }
830
831           if (matchedFile != null || entry.getFile() != null)
832           {
833             if (entry.getFile() != null)
834             {
835               // use entry's file
836               matchedFile = entry.getFile();
837             }
838             pdb.setFile(matchedFile); // entry.getFile());
839             if (pdbfiles == null)
840             {
841               pdbfiles = new ArrayList<String>();
842             }
843
844             if (!pdbfiles.contains(pdbId))
845             {
846               pdbfiles.add(pdbId);
847               copyFileToJar(jout, matchedFile, pdbId);
848             }
849           }
850
851           if (entry.getProperty() != null && !entry.getProperty().isEmpty())
852           {
853             PdbentryItem item = new PdbentryItem();
854             Hashtable properties = entry.getProperty();
855             Enumeration en2 = properties.keys();
856             while (en2.hasMoreElements())
857             {
858               Property prop = new Property();
859               String key = en2.nextElement().toString();
860               prop.setName(key);
861               prop.setValue(properties.get(key).toString());
862               item.addProperty(prop);
863             }
864             pdb.addPdbentryItem(item);
865           }
866
867           jseq.addPdbids(pdb);
868         }
869       }
870
871       saveRnaViewers(jout, jseq, jds, viewIds, ap, storeDS);
872
873       jms.addJSeq(jseq);
874     }
875
876     if (!storeDS && av.hasHiddenRows())
877     {
878       jal = av.getAlignment();
879     }
880     // SAVE MAPPINGS
881     if (jal.getCodonFrames() != null)
882     {
883       List<AlignedCodonFrame> jac = jal.getCodonFrames();
884       for (AlignedCodonFrame acf : jac)
885       {
886         AlcodonFrame alc = new AlcodonFrame();
887         vamsasSet.addAlcodonFrame(alc);
888         if (acf.getProtMappings() != null
889                 && acf.getProtMappings().length > 0)
890         {
891           SequenceI[] dnas = acf.getdnaSeqs();
892           jalview.datamodel.Mapping[] pmaps = acf.getProtMappings();
893           for (int m = 0; m < pmaps.length; m++)
894           {
895             AlcodMap alcmap = new AlcodMap();
896             alcmap.setDnasq(seqHash(dnas[m]));
897             alcmap.setMapping(createVamsasMapping(pmaps[m], dnas[m], null,
898                     false));
899             alc.addAlcodMap(alcmap);
900           }
901         }
902
903         // {
904         // AlcodonFrame alc = new AlcodonFrame();
905         // vamsasSet.addAlcodonFrame(alc);
906         // for (int p = 0; p < acf.aaWidth; p++)
907         // {
908         // Alcodon cmap = new Alcodon();
909         // if (acf.codons[p] != null)
910         // {
911         // // Null codons indicate a gapped column in the translated peptide
912         // // alignment.
913         // cmap.setPos1(acf.codons[p][0]);
914         // cmap.setPos2(acf.codons[p][1]);
915         // cmap.setPos3(acf.codons[p][2]);
916         // }
917         // alc.addAlcodon(cmap);
918         // }
919         // if (acf.getProtMappings() != null
920         // && acf.getProtMappings().length > 0)
921         // {
922         // SequenceI[] dnas = acf.getdnaSeqs();
923         // jalview.datamodel.Mapping[] pmaps = acf.getProtMappings();
924         // for (int m = 0; m < pmaps.length; m++)
925         // {
926         // AlcodMap alcmap = new AlcodMap();
927         // alcmap.setDnasq(seqHash(dnas[m]));
928         // alcmap.setMapping(createVamsasMapping(pmaps[m], dnas[m], null,
929         // false));
930         // alc.addAlcodMap(alcmap);
931         // }
932         // }
933       }
934     }
935
936     // SAVE TREES
937     // /////////////////////////////////
938     if (!storeDS && av.currentTree != null)
939     {
940       // FIND ANY ASSOCIATED TREES
941       // NOT IMPLEMENTED FOR HEADLESS STATE AT PRESENT
942       if (Desktop.desktop != null)
943       {
944         JInternalFrame[] frames = Desktop.desktop.getAllFrames();
945
946         for (int t = 0; t < frames.length; t++)
947         {
948           if (frames[t] instanceof TreePanel)
949           {
950             TreePanel tp = (TreePanel) frames[t];
951
952             if (tp.treeCanvas.av.getAlignment() == jal)
953             {
954               Tree tree = new Tree();
955               tree.setTitle(tp.getTitle());
956               tree.setCurrentTree((av.currentTree == tp.getTree()));
957               tree.setNewick(tp.getTree().toString());
958               tree.setThreshold(tp.treeCanvas.threshold);
959
960               tree.setFitToWindow(tp.fitToWindow.getState());
961               tree.setFontName(tp.getTreeFont().getName());
962               tree.setFontSize(tp.getTreeFont().getSize());
963               tree.setFontStyle(tp.getTreeFont().getStyle());
964               tree.setMarkUnlinked(tp.placeholdersMenu.getState());
965
966               tree.setShowBootstrap(tp.bootstrapMenu.getState());
967               tree.setShowDistances(tp.distanceMenu.getState());
968
969               tree.setHeight(tp.getHeight());
970               tree.setWidth(tp.getWidth());
971               tree.setXpos(tp.getX());
972               tree.setYpos(tp.getY());
973               tree.setId(makeHashCode(tp, null));
974               jms.addTree(tree);
975             }
976           }
977         }
978       }
979     }
980
981     // SAVE ANNOTATIONS
982     /**
983      * store forward refs from an annotationRow to any groups
984      */
985     IdentityHashMap<SequenceGroup, String> groupRefs = new IdentityHashMap<SequenceGroup, String>();
986     if (storeDS)
987     {
988       for (SequenceI sq : jal.getSequences())
989       {
990         // Store annotation on dataset sequences only
991         AlignmentAnnotation[] aa = sq.getAnnotation();
992         if (aa != null && aa.length > 0)
993         {
994           storeAlignmentAnnotation(aa, groupRefs, av, calcIdSet, storeDS,
995                   vamsasSet);
996         }
997       }
998     }
999     else
1000     {
1001       if (jal.getAlignmentAnnotation() != null)
1002       {
1003         // Store the annotation shown on the alignment.
1004         AlignmentAnnotation[] aa = jal.getAlignmentAnnotation();
1005         storeAlignmentAnnotation(aa, groupRefs, av, calcIdSet, storeDS,
1006                 vamsasSet);
1007       }
1008     }
1009     // SAVE GROUPS
1010     if (jal.getGroups() != null)
1011     {
1012       JGroup[] groups = new JGroup[jal.getGroups().size()];
1013       int i = -1;
1014       for (jalview.datamodel.SequenceGroup sg : jal.getGroups())
1015       {
1016         JGroup jGroup = new JGroup();
1017         groups[++i] = jGroup;
1018
1019         jGroup.setStart(sg.getStartRes());
1020         jGroup.setEnd(sg.getEndRes());
1021         jGroup.setName(sg.getName());
1022         if (groupRefs.containsKey(sg))
1023         {
1024           // group has references so set its ID field
1025           jGroup.setId(groupRefs.get(sg));
1026         }
1027         if (sg.cs != null)
1028         {
1029           if (sg.cs.conservationApplied())
1030           {
1031             jGroup.setConsThreshold(sg.cs.getConservationInc());
1032
1033             if (sg.cs instanceof jalview.schemes.UserColourScheme)
1034             {
1035               jGroup.setColour(setUserColourScheme(sg.cs, userColours, jms));
1036             }
1037             else
1038             {
1039               jGroup.setColour(ColourSchemeProperty.getColourName(sg.cs));
1040             }
1041           }
1042           else if (sg.cs instanceof jalview.schemes.AnnotationColourGradient)
1043           {
1044             jGroup.setColour("AnnotationColourGradient");
1045             jGroup.setAnnotationColours(constructAnnotationColours(
1046                     (jalview.schemes.AnnotationColourGradient) sg.cs,
1047                     userColours, jms));
1048           }
1049           else if (sg.cs instanceof jalview.schemes.UserColourScheme)
1050           {
1051             jGroup.setColour(setUserColourScheme(sg.cs, userColours, jms));
1052           }
1053           else
1054           {
1055             jGroup.setColour(ColourSchemeProperty.getColourName(sg.cs));
1056           }
1057
1058           jGroup.setPidThreshold(sg.cs.getThreshold());
1059         }
1060
1061         jGroup.setOutlineColour(sg.getOutlineColour().getRGB());
1062         jGroup.setDisplayBoxes(sg.getDisplayBoxes());
1063         jGroup.setDisplayText(sg.getDisplayText());
1064         jGroup.setColourText(sg.getColourText());
1065         jGroup.setTextCol1(sg.textColour.getRGB());
1066         jGroup.setTextCol2(sg.textColour2.getRGB());
1067         jGroup.setTextColThreshold(sg.thresholdTextColour);
1068         jGroup.setShowUnconserved(sg.getShowNonconserved());
1069         jGroup.setIgnoreGapsinConsensus(sg.getIgnoreGapsConsensus());
1070         jGroup.setShowConsensusHistogram(sg.isShowConsensusHistogram());
1071         jGroup.setShowSequenceLogo(sg.isShowSequenceLogo());
1072         jGroup.setNormaliseSequenceLogo(sg.isNormaliseSequenceLogo());
1073         for (SequenceI seq : sg.getSequences())
1074         {
1075           jGroup.addSeq(seqHash(seq));
1076         }
1077       }
1078
1079       jms.setJGroup(groups);
1080     }
1081     if (!storeDS)
1082     {
1083       // /////////SAVE VIEWPORT
1084       Viewport view = new Viewport();
1085       view.setTitle(ap.alignFrame.getTitle());
1086       view.setSequenceSetId(makeHashCode(av.getSequenceSetId(),
1087               av.getSequenceSetId()));
1088       view.setId(av.getViewId());
1089       if (av.getCodingComplement() != null)
1090       {
1091         view.setComplementId(av.getCodingComplement().getViewId());
1092       }
1093       view.setViewName(av.viewName);
1094       view.setGatheredViews(av.isGatherViewsHere());
1095
1096       Rectangle size = ap.av.getExplodedGeometry();
1097       Rectangle position = size;
1098       if (size == null)
1099       {
1100         size = ap.alignFrame.getBounds();
1101         if (av.getCodingComplement() != null)
1102         {
1103           position = ((SplitFrame) ap.alignFrame.getSplitViewContainer())
1104                   .getBounds();
1105         }
1106         else
1107         {
1108           position = size;
1109         }
1110       }
1111       view.setXpos(position.x);
1112       view.setYpos(position.y);
1113
1114       view.setWidth(size.width);
1115       view.setHeight(size.height);
1116
1117       view.setStartRes(av.startRes);
1118       view.setStartSeq(av.startSeq);
1119
1120       if (av.getGlobalColourScheme() instanceof jalview.schemes.UserColourScheme)
1121       {
1122         view.setBgColour(setUserColourScheme(av.getGlobalColourScheme(),
1123                 userColours, jms));
1124       }
1125       else if (av.getGlobalColourScheme() instanceof jalview.schemes.AnnotationColourGradient)
1126       {
1127         AnnotationColours ac = constructAnnotationColours(
1128                 (jalview.schemes.AnnotationColourGradient) av
1129                         .getGlobalColourScheme(),
1130                 userColours, jms);
1131
1132         view.setAnnotationColours(ac);
1133         view.setBgColour("AnnotationColourGradient");
1134       }
1135       else
1136       {
1137         view.setBgColour(ColourSchemeProperty.getColourName(av
1138                 .getGlobalColourScheme()));
1139       }
1140
1141       ColourSchemeI cs = av.getGlobalColourScheme();
1142
1143       if (cs != null)
1144       {
1145         if (cs.conservationApplied())
1146         {
1147           view.setConsThreshold(cs.getConservationInc());
1148           if (cs instanceof jalview.schemes.UserColourScheme)
1149           {
1150             view.setBgColour(setUserColourScheme(cs, userColours, jms));
1151           }
1152         }
1153
1154         if (cs instanceof ResidueColourScheme)
1155         {
1156           view.setPidThreshold(cs.getThreshold());
1157         }
1158       }
1159
1160       view.setConservationSelected(av.getConservationSelected());
1161       view.setPidSelected(av.getAbovePIDThreshold());
1162       view.setFontName(av.font.getName());
1163       view.setFontSize(av.font.getSize());
1164       view.setFontStyle(av.font.getStyle());
1165       view.setScaleProteinAsCdna(av.getViewStyle().isScaleProteinAsCdna());
1166       view.setRenderGaps(av.isRenderGaps());
1167       view.setShowAnnotation(av.isShowAnnotation());
1168       view.setShowBoxes(av.getShowBoxes());
1169       view.setShowColourText(av.getColourText());
1170       view.setShowFullId(av.getShowJVSuffix());
1171       view.setRightAlignIds(av.isRightAlignIds());
1172       view.setShowSequenceFeatures(av.isShowSequenceFeatures());
1173       view.setShowText(av.getShowText());
1174       view.setShowUnconserved(av.getShowUnconserved());
1175       view.setWrapAlignment(av.getWrapAlignment());
1176       view.setTextCol1(av.getTextColour().getRGB());
1177       view.setTextCol2(av.getTextColour2().getRGB());
1178       view.setTextColThreshold(av.getThresholdTextColour());
1179       view.setShowConsensusHistogram(av.isShowConsensusHistogram());
1180       view.setShowSequenceLogo(av.isShowSequenceLogo());
1181       view.setNormaliseSequenceLogo(av.isNormaliseSequenceLogo());
1182       view.setShowGroupConsensus(av.isShowGroupConsensus());
1183       view.setShowGroupConservation(av.isShowGroupConservation());
1184       view.setShowNPfeatureTooltip(av.isShowNPFeats());
1185       view.setShowDbRefTooltip(av.isShowDBRefs());
1186       view.setFollowHighlight(av.isFollowHighlight());
1187       view.setFollowSelection(av.followSelection);
1188       view.setIgnoreGapsinConsensus(av.isIgnoreGapsConsensus());
1189       if (av.getFeaturesDisplayed() != null)
1190       {
1191         jalview.schemabinding.version2.FeatureSettings fs = new jalview.schemabinding.version2.FeatureSettings();
1192
1193         String[] renderOrder = ap.getSeqPanel().seqCanvas
1194                 .getFeatureRenderer().getRenderOrder()
1195                 .toArray(new String[0]);
1196
1197         Vector settingsAdded = new Vector();
1198         Object gstyle = null;
1199         GraduatedColor gcol = null;
1200         if (renderOrder != null)
1201         {
1202           for (int ro = 0; ro < renderOrder.length; ro++)
1203           {
1204             gstyle = ap.getSeqPanel().seqCanvas.getFeatureRenderer()
1205                     .getFeatureStyle(renderOrder[ro]);
1206             Setting setting = new Setting();
1207             setting.setType(renderOrder[ro]);
1208             if (gstyle instanceof GraduatedColor)
1209             {
1210               gcol = (GraduatedColor) gstyle;
1211               setting.setColour(gcol.getMaxColor().getRGB());
1212               setting.setMincolour(gcol.getMinColor().getRGB());
1213               setting.setMin(gcol.getMin());
1214               setting.setMax(gcol.getMax());
1215               setting.setColourByLabel(gcol.isColourByLabel());
1216               setting.setAutoScale(gcol.isAutoScale());
1217               setting.setThreshold(gcol.getThresh());
1218               setting.setThreshstate(gcol.getThreshType());
1219             }
1220             else
1221             {
1222               setting.setColour(ap.getSeqPanel().seqCanvas
1223                       .getFeatureRenderer().getColour(renderOrder[ro])
1224                       .getRGB());
1225             }
1226
1227             setting.setDisplay(av.getFeaturesDisplayed().isVisible(
1228                     renderOrder[ro]));
1229             float rorder = ap.getSeqPanel().seqCanvas.getFeatureRenderer()
1230                     .getOrder(renderOrder[ro]);
1231             if (rorder > -1)
1232             {
1233               setting.setOrder(rorder);
1234             }
1235             fs.addSetting(setting);
1236             settingsAdded.addElement(renderOrder[ro]);
1237           }
1238         }
1239
1240         // Make sure we save none displayed feature settings
1241         Iterator en = ap.getSeqPanel().seqCanvas.getFeatureRenderer()
1242                 .getFeatureColours().keySet().iterator();
1243         while (en.hasNext())
1244         {
1245           String key = en.next().toString();
1246           if (settingsAdded.contains(key))
1247           {
1248             continue;
1249           }
1250
1251           Setting setting = new Setting();
1252           setting.setType(key);
1253           setting.setColour(ap.getSeqPanel().seqCanvas.getFeatureRenderer()
1254                   .getColour(key).getRGB());
1255
1256           setting.setDisplay(false);
1257           float rorder = ap.getSeqPanel().seqCanvas.getFeatureRenderer()
1258                   .getOrder(key);
1259           if (rorder > -1)
1260           {
1261             setting.setOrder(rorder);
1262           }
1263           fs.addSetting(setting);
1264           settingsAdded.addElement(key);
1265         }
1266         // is groups actually supposed to be a map here ?
1267         en = ap.getSeqPanel().seqCanvas.getFeatureRenderer()
1268                 .getFeatureGroups().iterator();
1269         Vector groupsAdded = new Vector();
1270         while (en.hasNext())
1271         {
1272           String grp = en.next().toString();
1273           if (groupsAdded.contains(grp))
1274           {
1275             continue;
1276           }
1277           Group g = new Group();
1278           g.setName(grp);
1279           g.setDisplay(((Boolean) ap.getSeqPanel().seqCanvas
1280                   .getFeatureRenderer().checkGroupVisibility(grp, false))
1281                   .booleanValue());
1282           fs.addGroup(g);
1283           groupsAdded.addElement(grp);
1284         }
1285         jms.setFeatureSettings(fs);
1286
1287       }
1288
1289       if (av.hasHiddenColumns())
1290       {
1291         if (av.getColumnSelection() == null
1292                 || av.getColumnSelection().getHiddenColumns() == null)
1293         {
1294           warn("REPORT BUG: avoided null columnselection bug (DMAM reported). Please contact Jim about this.");
1295         }
1296         else
1297         {
1298           for (int c = 0; c < av.getColumnSelection().getHiddenColumns()
1299                   .size(); c++)
1300           {
1301             int[] region = av.getColumnSelection().getHiddenColumns()
1302                     .get(c);
1303             HiddenColumns hc = new HiddenColumns();
1304             hc.setStart(region[0]);
1305             hc.setEnd(region[1]);
1306             view.addHiddenColumns(hc);
1307           }
1308         }
1309       }
1310       if (calcIdSet.size() > 0)
1311       {
1312         for (String calcId : calcIdSet)
1313         {
1314           if (calcId.trim().length() > 0)
1315           {
1316             CalcIdParam cidp = createCalcIdParam(calcId, av);
1317             // Some calcIds have no parameters.
1318             if (cidp != null)
1319             {
1320               view.addCalcIdParam(cidp);
1321             }
1322           }
1323         }
1324       }
1325
1326       jms.addViewport(view);
1327     }
1328     object.setJalviewModelSequence(jms);
1329     object.getVamsasModel().addSequenceSet(vamsasSet);
1330
1331     if (jout != null && fileName != null)
1332     {
1333       // We may not want to write the object to disk,
1334       // eg we can copy the alignViewport to a new view object
1335       // using save and then load
1336       try
1337       {
1338         System.out.println("Writing jar entry " + fileName);
1339         JarEntry entry = new JarEntry(fileName);
1340         jout.putNextEntry(entry);
1341         PrintWriter pout = new PrintWriter(new OutputStreamWriter(jout,
1342                 UTF_8));
1343         Marshaller marshaller = new Marshaller(pout);
1344         marshaller.marshal(object);
1345         pout.flush();
1346         jout.closeEntry();
1347       } catch (Exception ex)
1348       {
1349         // TODO: raise error in GUI if marshalling failed.
1350         ex.printStackTrace();
1351       }
1352     }
1353     return object;
1354   }
1355
1356   /**
1357    * Save any Varna viewers linked to this sequence. Writes an rnaViewer element
1358    * for each viewer, with
1359    * <ul>
1360    * <li>viewer geometry (position, size, split pane divider location)</li>
1361    * <li>index of the selected structure in the viewer (currently shows gapped
1362    * or ungapped)</li>
1363    * <li>the id of the annotation holding RNA secondary structure</li>
1364    * <li>(currently only one SS is shown per viewer, may be more in future)</li>
1365    * </ul>
1366    * Varna viewer state is also written out (in native Varna XML) to separate
1367    * project jar entries. A separate entry is written for each RNA structure
1368    * displayed, with the naming convention
1369    * <ul>
1370    * <li>rna_viewId_sequenceId_annotationId_[gapped|trimmed]</li>
1371    * </ul>
1372    * 
1373    * @param jout
1374    * @param jseq
1375    * @param jds
1376    * @param viewIds
1377    * @param ap
1378    * @param storeDataset
1379    */
1380   protected void saveRnaViewers(JarOutputStream jout, JSeq jseq,
1381           final SequenceI jds, List<String> viewIds, AlignmentPanel ap,
1382           boolean storeDataset)
1383   {
1384     if (Desktop.desktop == null)
1385     {
1386       return;
1387     }
1388     JInternalFrame[] frames = Desktop.desktop.getAllFrames();
1389     for (int f = frames.length - 1; f > -1; f--)
1390     {
1391       if (frames[f] instanceof AppVarna)
1392       {
1393         AppVarna varna = (AppVarna) frames[f];
1394         /*
1395          * link the sequence to every viewer that is showing it and is linked to
1396          * its alignment panel
1397          */
1398         if (varna.isListeningFor(jds) && ap == varna.getAlignmentPanel())
1399         {
1400           String viewId = varna.getViewId();
1401           RnaViewer rna = new RnaViewer();
1402           rna.setViewId(viewId);
1403           rna.setTitle(varna.getTitle());
1404           rna.setXpos(varna.getX());
1405           rna.setYpos(varna.getY());
1406           rna.setWidth(varna.getWidth());
1407           rna.setHeight(varna.getHeight());
1408           rna.setDividerLocation(varna.getDividerLocation());
1409           rna.setSelectedRna(varna.getSelectedIndex());
1410           jseq.addRnaViewer(rna);
1411
1412           /*
1413            * Store each Varna panel's state once in the project per sequence.
1414            * First time through only (storeDataset==false)
1415            */
1416           // boolean storeSessions = false;
1417           // String sequenceViewId = viewId + seqsToIds.get(jds);
1418           // if (!storeDataset && !viewIds.contains(sequenceViewId))
1419           // {
1420           // viewIds.add(sequenceViewId);
1421           // storeSessions = true;
1422           // }
1423           for (RnaModel model : varna.getModels())
1424           {
1425             if (model.seq == jds)
1426             {
1427               /*
1428                * VARNA saves each view (sequence or alignment secondary
1429                * structure, gapped or trimmed) as a separate XML file
1430                */
1431               String jarEntryName = rnaSessions.get(model);
1432               if (jarEntryName == null)
1433               {
1434
1435                 String varnaStateFile = varna.getStateInfo(model.rna);
1436                 jarEntryName = RNA_PREFIX + viewId + "_" + nextCounter();
1437                 copyFileToJar(jout, varnaStateFile, jarEntryName);
1438                 rnaSessions.put(model, jarEntryName);
1439               }
1440               SecondaryStructure ss = new SecondaryStructure();
1441               String annotationId = varna.getAnnotation(jds).annotationId;
1442               ss.setAnnotationId(annotationId);
1443               ss.setViewerState(jarEntryName);
1444               ss.setGapped(model.gapped);
1445               ss.setTitle(model.title);
1446               rna.addSecondaryStructure(ss);
1447             }
1448           }
1449         }
1450       }
1451     }
1452   }
1453
1454   /**
1455    * Copy the contents of a file to a new entry added to the output jar
1456    * 
1457    * @param jout
1458    * @param infilePath
1459    * @param jarEntryName
1460    */
1461   protected void copyFileToJar(JarOutputStream jout, String infilePath,
1462           String jarEntryName)
1463   {
1464     DataInputStream dis = null;
1465     try
1466     {
1467       File file = new File(infilePath);
1468       if (file.exists() && jout != null)
1469       {
1470         dis = new DataInputStream(new FileInputStream(file));
1471         byte[] data = new byte[(int) file.length()];
1472         dis.readFully(data);
1473         writeJarEntry(jout, jarEntryName, data);
1474       }
1475     } catch (Exception ex)
1476     {
1477       ex.printStackTrace();
1478     } finally
1479     {
1480       if (dis != null)
1481       {
1482         try
1483         {
1484           dis.close();
1485         } catch (IOException e)
1486         {
1487           // ignore
1488         }
1489       }
1490     }
1491   }
1492
1493   /**
1494    * Write the data to a new entry of given name in the output jar file
1495    * 
1496    * @param jout
1497    * @param jarEntryName
1498    * @param data
1499    * @throws IOException
1500    */
1501   protected void writeJarEntry(JarOutputStream jout, String jarEntryName,
1502           byte[] data) throws IOException
1503   {
1504     if (jout != null)
1505     {
1506       System.out.println("Writing jar entry " + jarEntryName);
1507       jout.putNextEntry(new JarEntry(jarEntryName));
1508       DataOutputStream dout = new DataOutputStream(jout);
1509       dout.write(data, 0, data.length);
1510       dout.flush();
1511       jout.closeEntry();
1512     }
1513   }
1514
1515   /**
1516    * Save the state of a structure viewer
1517    * 
1518    * @param ap
1519    * @param jds
1520    * @param pdb
1521    *          the archive XML element under which to save the state
1522    * @param entry
1523    * @param viewIds
1524    * @param matchedFile
1525    * @param viewFrame
1526    * @return
1527    */
1528   protected String saveStructureState(AlignmentPanel ap, SequenceI jds,
1529           Pdbids pdb, PDBEntry entry, List<String> viewIds,
1530           String matchedFile, StructureViewerBase viewFrame)
1531   {
1532     final AAStructureBindingModel bindingModel = viewFrame.getBinding();
1533
1534     /*
1535      * Look for any bindings for this viewer to the PDB file of interest
1536      * (including part matches excluding chain id)
1537      */
1538     for (int peid = 0; peid < bindingModel.getPdbCount(); peid++)
1539     {
1540       final PDBEntry pdbentry = bindingModel.getPdbEntry(peid);
1541       final String pdbId = pdbentry.getId();
1542       if (!pdbId.equals(entry.getId())
1543               && !(entry.getId().length() > 4 && entry.getId()
1544                       .toLowerCase().startsWith(pdbId.toLowerCase())))
1545       {
1546         /*
1547          * not interested in a binding to a different PDB entry here
1548          */
1549         continue;
1550       }
1551       if (matchedFile == null)
1552       {
1553         matchedFile = pdbentry.getFile();
1554       }
1555       else if (!matchedFile.equals(pdbentry.getFile()))
1556       {
1557         Cache.log
1558                 .warn("Probably lost some PDB-Sequence mappings for this structure file (which apparently has same PDB Entry code): "
1559                         + pdbentry.getFile());
1560       }
1561       // record the
1562       // file so we
1563       // can get at it if the ID
1564       // match is ambiguous (e.g.
1565       // 1QIP==1qipA)
1566
1567       for (int smap = 0; smap < viewFrame.getBinding().getSequence()[peid].length; smap++)
1568       {
1569         // if (jal.findIndex(jmol.jmb.sequence[peid][smap]) > -1)
1570         if (jds == viewFrame.getBinding().getSequence()[peid][smap])
1571         {
1572           StructureState state = new StructureState();
1573           state.setVisible(true);
1574           state.setXpos(viewFrame.getX());
1575           state.setYpos(viewFrame.getY());
1576           state.setWidth(viewFrame.getWidth());
1577           state.setHeight(viewFrame.getHeight());
1578           final String viewId = viewFrame.getViewId();
1579           state.setViewId(viewId);
1580           state.setAlignwithAlignPanel(viewFrame.isUsedforaligment(ap));
1581           state.setColourwithAlignPanel(viewFrame.isUsedforcolourby(ap));
1582           state.setColourByJmol(viewFrame.isColouredByViewer());
1583           state.setType(viewFrame.getViewerType().toString());
1584           pdb.addStructureState(state);
1585         }
1586       }
1587     }
1588     return matchedFile;
1589   }
1590
1591   private AnnotationColours constructAnnotationColours(
1592           AnnotationColourGradient acg, List<UserColourScheme> userColours,
1593           JalviewModelSequence jms)
1594   {
1595     AnnotationColours ac = new AnnotationColours();
1596     ac.setAboveThreshold(acg.getAboveThreshold());
1597     ac.setThreshold(acg.getAnnotationThreshold());
1598     ac.setAnnotation(acg.getAnnotation());
1599     if (acg.getBaseColour() instanceof jalview.schemes.UserColourScheme)
1600     {
1601       ac.setColourScheme(setUserColourScheme(acg.getBaseColour(),
1602               userColours, jms));
1603     }
1604     else
1605     {
1606       ac.setColourScheme(ColourSchemeProperty.getColourName(acg
1607               .getBaseColour()));
1608     }
1609
1610     ac.setMaxColour(acg.getMaxColour().getRGB());
1611     ac.setMinColour(acg.getMinColour().getRGB());
1612     ac.setPerSequence(acg.isSeqAssociated());
1613     ac.setPredefinedColours(acg.isPredefinedColours());
1614     return ac;
1615   }
1616
1617   private void storeAlignmentAnnotation(AlignmentAnnotation[] aa,
1618           IdentityHashMap<SequenceGroup, String> groupRefs,
1619           AlignmentViewport av, Set<String> calcIdSet, boolean storeDS,
1620           SequenceSet vamsasSet)
1621   {
1622
1623     for (int i = 0; i < aa.length; i++)
1624     {
1625       Annotation an = new Annotation();
1626
1627       AlignmentAnnotation annotation = aa[i];
1628       if (annotation.annotationId != null)
1629       {
1630         annotationIds.put(annotation.annotationId, annotation);
1631       }
1632
1633       an.setId(annotation.annotationId);
1634
1635       an.setVisible(annotation.visible);
1636
1637       an.setDescription(annotation.description);
1638
1639       if (annotation.sequenceRef != null)
1640       {
1641         // 2.9 JAL-1781 xref on sequence id rather than name
1642         an.setSequenceRef(seqsToIds.get(annotation.sequenceRef));
1643       }
1644       if (annotation.groupRef != null)
1645       {
1646         String groupIdr = groupRefs.get(annotation.groupRef);
1647         if (groupIdr == null)
1648         {
1649           // make a locally unique String
1650           groupRefs.put(
1651                   annotation.groupRef,
1652                   groupIdr = ("" + System.currentTimeMillis()
1653                           + annotation.groupRef.getName() + groupRefs
1654                           .size()));
1655         }
1656         an.setGroupRef(groupIdr.toString());
1657       }
1658
1659       // store all visualization attributes for annotation
1660       an.setGraphHeight(annotation.graphHeight);
1661       an.setCentreColLabels(annotation.centreColLabels);
1662       an.setScaleColLabels(annotation.scaleColLabel);
1663       an.setShowAllColLabels(annotation.showAllColLabels);
1664       an.setBelowAlignment(annotation.belowAlignment);
1665
1666       if (annotation.graph > 0)
1667       {
1668         an.setGraph(true);
1669         an.setGraphType(annotation.graph);
1670         an.setGraphGroup(annotation.graphGroup);
1671         if (annotation.getThreshold() != null)
1672         {
1673           ThresholdLine line = new ThresholdLine();
1674           line.setLabel(annotation.getThreshold().label);
1675           line.setValue(annotation.getThreshold().value);
1676           line.setColour(annotation.getThreshold().colour.getRGB());
1677           an.setThresholdLine(line);
1678         }
1679       }
1680       else
1681       {
1682         an.setGraph(false);
1683       }
1684
1685       an.setLabel(annotation.label);
1686
1687       if (annotation == av.getAlignmentQualityAnnot()
1688               || annotation == av.getAlignmentConservationAnnotation()
1689               || annotation == av.getAlignmentConsensusAnnotation()
1690               || annotation.autoCalculated)
1691       {
1692         // new way of indicating autocalculated annotation -
1693         an.setAutoCalculated(annotation.autoCalculated);
1694       }
1695       if (annotation.hasScore())
1696       {
1697         an.setScore(annotation.getScore());
1698       }
1699
1700       if (annotation.getCalcId() != null)
1701       {
1702         calcIdSet.add(annotation.getCalcId());
1703         an.setCalcId(annotation.getCalcId());
1704       }
1705       if (annotation.hasProperties())
1706       {
1707         for (String pr : annotation.getProperties())
1708         {
1709           Property prop = new Property();
1710           prop.setName(pr);
1711           prop.setValue(annotation.getProperty(pr));
1712           an.addProperty(prop);
1713         }
1714       }
1715
1716       AnnotationElement ae;
1717       if (annotation.annotations != null)
1718       {
1719         an.setScoreOnly(false);
1720         for (int a = 0; a < annotation.annotations.length; a++)
1721         {
1722           if ((annotation == null) || (annotation.annotations[a] == null))
1723           {
1724             continue;
1725           }
1726
1727           ae = new AnnotationElement();
1728           if (annotation.annotations[a].description != null)
1729           {
1730             ae.setDescription(annotation.annotations[a].description);
1731           }
1732           if (annotation.annotations[a].displayCharacter != null)
1733           {
1734             ae.setDisplayCharacter(annotation.annotations[a].displayCharacter);
1735           }
1736
1737           if (!Float.isNaN(annotation.annotations[a].value))
1738           {
1739             ae.setValue(annotation.annotations[a].value);
1740           }
1741
1742           ae.setPosition(a);
1743           if (annotation.annotations[a].secondaryStructure > ' ')
1744           {
1745             ae.setSecondaryStructure(annotation.annotations[a].secondaryStructure
1746                     + "");
1747           }
1748
1749           if (annotation.annotations[a].colour != null
1750                   && annotation.annotations[a].colour != java.awt.Color.black)
1751           {
1752             ae.setColour(annotation.annotations[a].colour.getRGB());
1753           }
1754
1755           an.addAnnotationElement(ae);
1756           if (annotation.autoCalculated)
1757           {
1758             // only write one non-null entry into the annotation row -
1759             // sufficient to get the visualization attributes necessary to
1760             // display data
1761             continue;
1762           }
1763         }
1764       }
1765       else
1766       {
1767         an.setScoreOnly(true);
1768       }
1769       if (!storeDS || (storeDS && !annotation.autoCalculated))
1770       {
1771         // skip autocalculated annotation - these are only provided for
1772         // alignments
1773         vamsasSet.addAnnotation(an);
1774       }
1775     }
1776
1777   }
1778
1779   private CalcIdParam createCalcIdParam(String calcId, AlignViewport av)
1780   {
1781     AutoCalcSetting settings = av.getCalcIdSettingsFor(calcId);
1782     if (settings != null)
1783     {
1784       CalcIdParam vCalcIdParam = new CalcIdParam();
1785       vCalcIdParam.setCalcId(calcId);
1786       vCalcIdParam.addServiceURL(settings.getServiceURI());
1787       // generic URI allowing a third party to resolve another instance of the
1788       // service used for this calculation
1789       for (String urls : settings.getServiceURLs())
1790       {
1791         vCalcIdParam.addServiceURL(urls);
1792       }
1793       vCalcIdParam.setVersion("1.0");
1794       if (settings.getPreset() != null)
1795       {
1796         WsParamSetI setting = settings.getPreset();
1797         vCalcIdParam.setName(setting.getName());
1798         vCalcIdParam.setDescription(setting.getDescription());
1799       }
1800       else
1801       {
1802         vCalcIdParam.setName("");
1803         vCalcIdParam.setDescription("Last used parameters");
1804       }
1805       // need to be able to recover 1) settings 2) user-defined presets or
1806       // recreate settings from preset 3) predefined settings provided by
1807       // service - or settings that can be transferred (or discarded)
1808       vCalcIdParam.setParameters(settings.getWsParamFile().replace("\n",
1809               "|\\n|"));
1810       vCalcIdParam.setAutoUpdate(settings.isAutoUpdate());
1811       // todo - decide if updateImmediately is needed for any projects.
1812
1813       return vCalcIdParam;
1814     }
1815     return null;
1816   }
1817
1818   private boolean recoverCalcIdParam(CalcIdParam calcIdParam,
1819           AlignViewport av)
1820   {
1821     if (calcIdParam.getVersion().equals("1.0"))
1822     {
1823       Jws2Instance service = Jws2Discoverer.getDiscoverer()
1824               .getPreferredServiceFor(calcIdParam.getServiceURL());
1825       if (service != null)
1826       {
1827         WsParamSetI parmSet = null;
1828         try
1829         {
1830           parmSet = service.getParamStore().parseServiceParameterFile(
1831                   calcIdParam.getName(), calcIdParam.getDescription(),
1832                   calcIdParam.getServiceURL(),
1833                   calcIdParam.getParameters().replace("|\\n|", "\n"));
1834         } catch (IOException x)
1835         {
1836           warn("Couldn't parse parameter data for "
1837                   + calcIdParam.getCalcId(), x);
1838           return false;
1839         }
1840         List<ArgumentI> argList = null;
1841         if (calcIdParam.getName().length() > 0)
1842         {
1843           parmSet = service.getParamStore()
1844                   .getPreset(calcIdParam.getName());
1845           if (parmSet != null)
1846           {
1847             // TODO : check we have a good match with settings in AACon -
1848             // otherwise we'll need to create a new preset
1849           }
1850         }
1851         else
1852         {
1853           argList = parmSet.getArguments();
1854           parmSet = null;
1855         }
1856         AAConSettings settings = new AAConSettings(
1857                 calcIdParam.isAutoUpdate(), service, parmSet, argList);
1858         av.setCalcIdSettingsFor(calcIdParam.getCalcId(), settings,
1859                 calcIdParam.isNeedsUpdate());
1860         return true;
1861       }
1862       else
1863       {
1864         warn("Cannot resolve a service for the parameters used in this project. Try configuring a JABAWS server.");
1865         return false;
1866       }
1867     }
1868     throw new Error(MessageManager.formatMessage(
1869             "error.unsupported_version_calcIdparam",
1870             new Object[] { calcIdParam.toString() }));
1871   }
1872
1873   /**
1874    * External mapping between jalview objects and objects yielding a valid and
1875    * unique object ID string. This is null for normal Jalview project IO, but
1876    * non-null when a jalview project is being read or written as part of a
1877    * vamsas session.
1878    */
1879   IdentityHashMap jv2vobj = null;
1880
1881   /**
1882    * Construct a unique ID for jvobj using either existing bindings or if none
1883    * exist, the result of the hashcode call for the object.
1884    * 
1885    * @param jvobj
1886    *          jalview data object
1887    * @return unique ID for referring to jvobj
1888    */
1889   private String makeHashCode(Object jvobj, String altCode)
1890   {
1891     if (jv2vobj != null)
1892     {
1893       Object id = jv2vobj.get(jvobj);
1894       if (id != null)
1895       {
1896         return id.toString();
1897       }
1898       // check string ID mappings
1899       if (jvids2vobj != null && jvobj instanceof String)
1900       {
1901         id = jvids2vobj.get(jvobj);
1902       }
1903       if (id != null)
1904       {
1905         return id.toString();
1906       }
1907       // give up and warn that something has gone wrong
1908       warn("Cannot find ID for object in external mapping : " + jvobj);
1909     }
1910     return altCode;
1911   }
1912
1913   /**
1914    * return local jalview object mapped to ID, if it exists
1915    * 
1916    * @param idcode
1917    *          (may be null)
1918    * @return null or object bound to idcode
1919    */
1920   private Object retrieveExistingObj(String idcode)
1921   {
1922     if (idcode != null && vobj2jv != null)
1923     {
1924       return vobj2jv.get(idcode);
1925     }
1926     return null;
1927   }
1928
1929   /**
1930    * binding from ID strings from external mapping table to jalview data model
1931    * objects.
1932    */
1933   private Hashtable vobj2jv;
1934
1935   private Sequence createVamsasSequence(String id, SequenceI jds)
1936   {
1937     return createVamsasSequence(true, id, jds, null);
1938   }
1939
1940   private Sequence createVamsasSequence(boolean recurse, String id,
1941           SequenceI jds, SequenceI parentseq)
1942   {
1943     Sequence vamsasSeq = new Sequence();
1944     vamsasSeq.setId(id);
1945     vamsasSeq.setName(jds.getName());
1946     vamsasSeq.setSequence(jds.getSequenceAsString());
1947     vamsasSeq.setDescription(jds.getDescription());
1948     jalview.datamodel.DBRefEntry[] dbrefs = null;
1949     if (jds.getDatasetSequence() != null)
1950     {
1951       vamsasSeq.setDsseqid(seqHash(jds.getDatasetSequence()));
1952       if (jds.getDatasetSequence().getDBRefs() != null)
1953       {
1954         dbrefs = jds.getDatasetSequence().getDBRefs();
1955       }
1956     }
1957     else
1958     {
1959       vamsasSeq.setDsseqid(id); // so we can tell which sequences really are
1960       // dataset sequences only
1961       dbrefs = jds.getDBRefs();
1962     }
1963     if (dbrefs != null)
1964     {
1965       for (int d = 0; d < dbrefs.length; d++)
1966       {
1967         DBRef dbref = new DBRef();
1968         dbref.setSource(dbrefs[d].getSource());
1969         dbref.setVersion(dbrefs[d].getVersion());
1970         dbref.setAccessionId(dbrefs[d].getAccessionId());
1971         if (dbrefs[d].hasMap())
1972         {
1973           Mapping mp = createVamsasMapping(dbrefs[d].getMap(), parentseq,
1974                   jds, recurse);
1975           dbref.setMapping(mp);
1976         }
1977         vamsasSeq.addDBRef(dbref);
1978       }
1979     }
1980     return vamsasSeq;
1981   }
1982
1983   private Mapping createVamsasMapping(jalview.datamodel.Mapping jmp,
1984           SequenceI parentseq, SequenceI jds, boolean recurse)
1985   {
1986     Mapping mp = null;
1987     if (jmp.getMap() != null)
1988     {
1989       mp = new Mapping();
1990
1991       jalview.util.MapList mlst = jmp.getMap();
1992       List<int[]> r = mlst.getFromRanges();
1993       for (int[] range : r)
1994       {
1995         MapListFrom mfrom = new MapListFrom();
1996         mfrom.setStart(range[0]);
1997         mfrom.setEnd(range[1]);
1998         mp.addMapListFrom(mfrom);
1999       }
2000       r = mlst.getToRanges();
2001       for (int[] range : r)
2002       {
2003         MapListTo mto = new MapListTo();
2004         mto.setStart(range[0]);
2005         mto.setEnd(range[1]);
2006         mp.addMapListTo(mto);
2007       }
2008       mp.setMapFromUnit(mlst.getFromRatio());
2009       mp.setMapToUnit(mlst.getToRatio());
2010       if (jmp.getTo() != null)
2011       {
2012         MappingChoice mpc = new MappingChoice();
2013         if (recurse
2014                 && (parentseq != jmp.getTo() || parentseq
2015                         .getDatasetSequence() != jmp.getTo()))
2016         {
2017           mpc.setSequence(createVamsasSequence(false, seqHash(jmp.getTo()),
2018                   jmp.getTo(), jds));
2019         }
2020         else
2021         {
2022           String jmpid = "";
2023           SequenceI ps = null;
2024           if (parentseq != jmp.getTo()
2025                   && parentseq.getDatasetSequence() != jmp.getTo())
2026           {
2027             // chaining dbref rather than a handshaking one
2028             jmpid = seqHash(ps = jmp.getTo());
2029           }
2030           else
2031           {
2032             jmpid = seqHash(ps = parentseq);
2033           }
2034           mpc.setDseqFor(jmpid);
2035           if (!seqRefIds.containsKey(mpc.getDseqFor()))
2036           {
2037             jalview.bin.Cache.log.debug("creatign new DseqFor ID");
2038             seqRefIds.put(mpc.getDseqFor(), ps);
2039           }
2040           else
2041           {
2042             jalview.bin.Cache.log.debug("reusing DseqFor ID");
2043           }
2044         }
2045         mp.setMappingChoice(mpc);
2046       }
2047     }
2048     return mp;
2049   }
2050
2051   String setUserColourScheme(jalview.schemes.ColourSchemeI cs,
2052           List<UserColourScheme> userColours, JalviewModelSequence jms)
2053   {
2054     String id = null;
2055     jalview.schemes.UserColourScheme ucs = (jalview.schemes.UserColourScheme) cs;
2056     boolean newucs = false;
2057     if (!userColours.contains(ucs))
2058     {
2059       userColours.add(ucs);
2060       newucs = true;
2061     }
2062     id = "ucs" + userColours.indexOf(ucs);
2063     if (newucs)
2064     {
2065       // actually create the scheme's entry in the XML model
2066       java.awt.Color[] colours = ucs.getColours();
2067       jalview.schemabinding.version2.UserColours uc = new jalview.schemabinding.version2.UserColours();
2068       jalview.schemabinding.version2.UserColourScheme jbucs = new jalview.schemabinding.version2.UserColourScheme();
2069
2070       for (int i = 0; i < colours.length; i++)
2071       {
2072         jalview.schemabinding.version2.Colour col = new jalview.schemabinding.version2.Colour();
2073         col.setName(ResidueProperties.aa[i]);
2074         col.setRGB(jalview.util.Format.getHexString(colours[i]));
2075         jbucs.addColour(col);
2076       }
2077       if (ucs.getLowerCaseColours() != null)
2078       {
2079         colours = ucs.getLowerCaseColours();
2080         for (int i = 0; i < colours.length; i++)
2081         {
2082           jalview.schemabinding.version2.Colour col = new jalview.schemabinding.version2.Colour();
2083           col.setName(ResidueProperties.aa[i].toLowerCase());
2084           col.setRGB(jalview.util.Format.getHexString(colours[i]));
2085           jbucs.addColour(col);
2086         }
2087       }
2088
2089       uc.setId(id);
2090       uc.setUserColourScheme(jbucs);
2091       jms.addUserColours(uc);
2092     }
2093
2094     return id;
2095   }
2096
2097   jalview.schemes.UserColourScheme getUserColourScheme(
2098           JalviewModelSequence jms, String id)
2099   {
2100     UserColours[] uc = jms.getUserColours();
2101     UserColours colours = null;
2102
2103     for (int i = 0; i < uc.length; i++)
2104     {
2105       if (uc[i].getId().equals(id))
2106       {
2107         colours = uc[i];
2108
2109         break;
2110       }
2111     }
2112
2113     java.awt.Color[] newColours = new java.awt.Color[24];
2114
2115     for (int i = 0; i < 24; i++)
2116     {
2117       newColours[i] = new java.awt.Color(Integer.parseInt(colours
2118               .getUserColourScheme().getColour(i).getRGB(), 16));
2119     }
2120
2121     jalview.schemes.UserColourScheme ucs = new jalview.schemes.UserColourScheme(
2122             newColours);
2123
2124     if (colours.getUserColourScheme().getColourCount() > 24)
2125     {
2126       newColours = new java.awt.Color[23];
2127       for (int i = 0; i < 23; i++)
2128       {
2129         newColours[i] = new java.awt.Color(Integer.parseInt(colours
2130                 .getUserColourScheme().getColour(i + 24).getRGB(), 16));
2131       }
2132       ucs.setLowerCaseColours(newColours);
2133     }
2134
2135     return ucs;
2136   }
2137
2138   /**
2139    * contains last error message (if any) encountered by XML loader.
2140    */
2141   String errorMessage = null;
2142
2143   /**
2144    * flag to control whether the Jalview2XML_V1 parser should be deferred to if
2145    * exceptions are raised during project XML parsing
2146    */
2147   public boolean attemptversion1parse = true;
2148
2149   /**
2150    * Load a jalview project archive from a jar file
2151    * 
2152    * @param file
2153    *          - HTTP URL or filename
2154    */
2155   public AlignFrame loadJalviewAlign(final String file)
2156   {
2157
2158     jalview.gui.AlignFrame af = null;
2159
2160     try
2161     {
2162       // create list to store references for any new Jmol viewers created
2163       newStructureViewers = new Vector<JalviewStructureDisplayI>();
2164       // UNMARSHALLER SEEMS TO CLOSE JARINPUTSTREAM, MOST ANNOYING
2165       // Workaround is to make sure caller implements the JarInputStreamProvider
2166       // interface
2167       // so we can re-open the jar input stream for each entry.
2168
2169       jarInputStreamProvider jprovider = createjarInputStreamProvider(file);
2170       af = loadJalviewAlign(jprovider);
2171
2172     } catch (MalformedURLException e)
2173     {
2174       errorMessage = "Invalid URL format for '" + file + "'";
2175       reportErrors();
2176     } finally
2177     {
2178       try
2179       {
2180         SwingUtilities.invokeAndWait(new Runnable()
2181         {
2182           @Override
2183           public void run()
2184           {
2185             setLoadingFinishedForNewStructureViewers();
2186           };
2187         });
2188       } catch (Exception x)
2189       {
2190         System.err.println("Error loading alignment: " + x.getMessage());
2191       }
2192     }
2193     return af;
2194   }
2195
2196   private jarInputStreamProvider createjarInputStreamProvider(
2197           final String file) throws MalformedURLException
2198   {
2199     URL url = null;
2200     errorMessage = null;
2201     uniqueSetSuffix = null;
2202     seqRefIds = null;
2203     viewportsAdded.clear();
2204     frefedSequence = null;
2205
2206     if (file.startsWith("http://"))
2207     {
2208       url = new URL(file);
2209     }
2210     final URL _url = url;
2211     return new jarInputStreamProvider()
2212     {
2213
2214       @Override
2215       public JarInputStream getJarInputStream() throws IOException
2216       {
2217         if (_url != null)
2218         {
2219           return new JarInputStream(_url.openStream());
2220         }
2221         else
2222         {
2223           return new JarInputStream(new FileInputStream(file));
2224         }
2225       }
2226
2227       @Override
2228       public String getFilename()
2229       {
2230         return file;
2231       }
2232     };
2233   }
2234
2235   /**
2236    * Recover jalview session from a jalview project archive. Caller may
2237    * initialise uniqueSetSuffix, seqRefIds, viewportsAdded and frefedSequence
2238    * themselves. Any null fields will be initialised with default values,
2239    * non-null fields are left alone.
2240    * 
2241    * @param jprovider
2242    * @return
2243    */
2244   public AlignFrame loadJalviewAlign(final jarInputStreamProvider jprovider)
2245   {
2246     errorMessage = null;
2247     if (uniqueSetSuffix == null)
2248     {
2249       uniqueSetSuffix = System.currentTimeMillis() % 100000 + "";
2250     }
2251     if (seqRefIds == null)
2252     {
2253       seqRefIds = new HashMap<String, SequenceI>();
2254     }
2255     if (frefedSequence == null)
2256     {
2257       frefedSequence = new Vector<Object[]>();
2258     }
2259
2260     AlignFrame af = null, _af = null;
2261     Map<String, AlignFrame> gatherToThisFrame = new HashMap<String, AlignFrame>();
2262     final String file = jprovider.getFilename();
2263     try
2264     {
2265       JarInputStream jin = null;
2266       JarEntry jarentry = null;
2267       int entryCount = 1;
2268
2269       do
2270       {
2271         jin = jprovider.getJarInputStream();
2272         for (int i = 0; i < entryCount; i++)
2273         {
2274           jarentry = jin.getNextJarEntry();
2275         }
2276
2277         if (jarentry != null && jarentry.getName().endsWith(".xml"))
2278         {
2279           InputStreamReader in = new InputStreamReader(jin, UTF_8);
2280           JalviewModel object = new JalviewModel();
2281
2282           Unmarshaller unmar = new Unmarshaller(object);
2283           unmar.setValidation(false);
2284           object = (JalviewModel) unmar.unmarshal(in);
2285           if (true) // !skipViewport(object))
2286           {
2287             _af = loadFromObject(object, file, true, jprovider);
2288             if (object.getJalviewModelSequence().getViewportCount() > 0)
2289             {
2290               af = _af;
2291               if (af.viewport.isGatherViewsHere())
2292               {
2293                 gatherToThisFrame.put(af.viewport.getSequenceSetId(), af);
2294               }
2295             }
2296           }
2297           entryCount++;
2298         }
2299         else if (jarentry != null)
2300         {
2301           // Some other file here.
2302           entryCount++;
2303         }
2304       } while (jarentry != null);
2305       resolveFrefedSequences();
2306     } catch (IOException ex)
2307     {
2308       ex.printStackTrace();
2309       errorMessage = "Couldn't locate Jalview XML file : " + file;
2310       System.err.println("Exception whilst loading jalview XML file : "
2311               + ex + "\n");
2312     } catch (Exception ex)
2313     {
2314       System.err.println("Parsing as Jalview Version 2 file failed.");
2315       ex.printStackTrace(System.err);
2316       if (attemptversion1parse)
2317       {
2318         // Is Version 1 Jar file?
2319         try
2320         {
2321           af = new Jalview2XML_V1(raiseGUI).LoadJalviewAlign(jprovider);
2322         } catch (Exception ex2)
2323         {
2324           System.err.println("Exception whilst loading as jalviewXMLV1:");
2325           ex2.printStackTrace();
2326           af = null;
2327         }
2328       }
2329       if (Desktop.instance != null)
2330       {
2331         Desktop.instance.stopLoading();
2332       }
2333       if (af != null)
2334       {
2335         System.out.println("Successfully loaded archive file");
2336         return af;
2337       }
2338       ex.printStackTrace();
2339
2340       System.err.println("Exception whilst loading jalview XML file : "
2341               + ex + "\n");
2342     } catch (OutOfMemoryError e)
2343     {
2344       // Don't use the OOM Window here
2345       errorMessage = "Out of memory loading jalview XML file";
2346       System.err.println("Out of memory whilst loading jalview XML file");
2347       e.printStackTrace();
2348     }
2349
2350     if (Desktop.instance != null)
2351     {
2352       Desktop.instance.stopLoading();
2353     }
2354
2355     /*
2356      * Regather multiple views (with the same sequence set id) to the frame (if
2357      * any) that is flagged as the one to gather to, i.e. convert them to tabbed
2358      * views instead of separate frames. Note this doesn't restore a state where
2359      * some expanded views in turn have tabbed views - the last "first tab" read
2360      * in will play the role of gatherer for all.
2361      */
2362     for (AlignFrame fr : gatherToThisFrame.values())
2363     {
2364       Desktop.instance.gatherViews(fr);
2365     }
2366
2367     restoreSplitFrames();
2368
2369     if (errorMessage != null)
2370     {
2371       reportErrors();
2372     }
2373     return af;
2374   }
2375
2376   /**
2377    * Try to reconstruct and display SplitFrame windows, where each contains
2378    * complementary dna and protein alignments. Done by pairing up AlignFrame
2379    * objects (created earlier) which have complementary viewport ids associated.
2380    */
2381   protected void restoreSplitFrames()
2382   {
2383     List<SplitFrame> gatherTo = new ArrayList<SplitFrame>();
2384     List<AlignFrame> addedToSplitFrames = new ArrayList<AlignFrame>();
2385     Map<String, AlignFrame> dna = new HashMap<String, AlignFrame>();
2386
2387     /*
2388      * Identify the DNA alignments
2389      */
2390     for (Entry<Viewport, AlignFrame> candidate : splitFrameCandidates
2391             .entrySet())
2392     {
2393       AlignFrame af = candidate.getValue();
2394       if (af.getViewport().getAlignment().isNucleotide())
2395       {
2396         dna.put(candidate.getKey().getId(), af);
2397       }
2398     }
2399
2400     /*
2401      * Try to match up the protein complements
2402      */
2403     for (Entry<Viewport, AlignFrame> candidate : splitFrameCandidates
2404             .entrySet())
2405     {
2406       AlignFrame af = candidate.getValue();
2407       if (!af.getViewport().getAlignment().isNucleotide())
2408       {
2409         String complementId = candidate.getKey().getComplementId();
2410         // only non-null complements should be in the Map
2411         if (complementId != null && dna.containsKey(complementId))
2412         {
2413           final AlignFrame dnaFrame = dna.get(complementId);
2414           SplitFrame sf = createSplitFrame(dnaFrame, af);
2415           addedToSplitFrames.add(dnaFrame);
2416           addedToSplitFrames.add(af);
2417           if (af.viewport.isGatherViewsHere())
2418           {
2419             gatherTo.add(sf);
2420           }
2421         }
2422       }
2423     }
2424
2425     /*
2426      * Open any that we failed to pair up (which shouldn't happen!) as
2427      * standalone AlignFrame's.
2428      */
2429     for (Entry<Viewport, AlignFrame> candidate : splitFrameCandidates
2430             .entrySet())
2431     {
2432       AlignFrame af = candidate.getValue();
2433       if (!addedToSplitFrames.contains(af))
2434       {
2435         Viewport view = candidate.getKey();
2436         Desktop.addInternalFrame(af, view.getTitle(), view.getWidth(),
2437                 view.getHeight());
2438         System.err.println("Failed to restore view " + view.getTitle()
2439                 + " to split frame");
2440       }
2441     }
2442
2443     /*
2444      * Gather back into tabbed views as flagged.
2445      */
2446     for (SplitFrame sf : gatherTo)
2447     {
2448       Desktop.instance.gatherViews(sf);
2449     }
2450
2451     splitFrameCandidates.clear();
2452   }
2453
2454   /**
2455    * Construct and display one SplitFrame holding DNA and protein alignments.
2456    * 
2457    * @param dnaFrame
2458    * @param proteinFrame
2459    * @return
2460    */
2461   protected SplitFrame createSplitFrame(AlignFrame dnaFrame,
2462           AlignFrame proteinFrame)
2463   {
2464     SplitFrame splitFrame = new SplitFrame(dnaFrame, proteinFrame);
2465     String title = MessageManager.getString("label.linked_view_title");
2466     int width = (int) dnaFrame.getBounds().getWidth();
2467     int height = (int) (dnaFrame.getBounds().getHeight()
2468             + proteinFrame.getBounds().getHeight() + 50);
2469
2470     /*
2471      * SplitFrame location is saved to both enclosed frames
2472      */
2473     splitFrame.setLocation(dnaFrame.getX(), dnaFrame.getY());
2474     Desktop.addInternalFrame(splitFrame, title, width, height);
2475
2476     /*
2477      * And compute cDNA consensus (couldn't do earlier with consensus as
2478      * mappings were not yet present)
2479      */
2480     proteinFrame.viewport.alignmentChanged(proteinFrame.alignPanel);
2481
2482     return splitFrame;
2483   }
2484
2485   /**
2486    * check errorMessage for a valid error message and raise an error box in the
2487    * GUI or write the current errorMessage to stderr and then clear the error
2488    * state.
2489    */
2490   protected void reportErrors()
2491   {
2492     reportErrors(false);
2493   }
2494
2495   protected void reportErrors(final boolean saving)
2496   {
2497     if (errorMessage != null)
2498     {
2499       final String finalErrorMessage = errorMessage;
2500       if (raiseGUI)
2501       {
2502         javax.swing.SwingUtilities.invokeLater(new Runnable()
2503         {
2504           @Override
2505           public void run()
2506           {
2507             JOptionPane.showInternalMessageDialog(Desktop.desktop,
2508                     finalErrorMessage, "Error "
2509                             + (saving ? "saving" : "loading")
2510                             + " Jalview file", JOptionPane.WARNING_MESSAGE);
2511           }
2512         });
2513       }
2514       else
2515       {
2516         System.err.println("Problem loading Jalview file: " + errorMessage);
2517       }
2518     }
2519     errorMessage = null;
2520   }
2521
2522   Map<String, String> alreadyLoadedPDB = new HashMap<String, String>();
2523
2524   /**
2525    * when set, local views will be updated from view stored in JalviewXML
2526    * Currently (28th Sep 2008) things will go horribly wrong in vamsas document
2527    * sync if this is set to true.
2528    */
2529   private final boolean updateLocalViews = false;
2530
2531   /**
2532    * Returns the path to a temporary file holding the PDB file for the given PDB
2533    * id. The first time of asking, searches for a file of that name in the
2534    * Jalview project jar, and copies it to a new temporary file. Any repeat
2535    * requests just return the path to the file previously created.
2536    * 
2537    * @param jprovider
2538    * @param pdbId
2539    * @return
2540    */
2541   String loadPDBFile(jarInputStreamProvider jprovider, String pdbId)
2542   {
2543     if (alreadyLoadedPDB.containsKey(pdbId))
2544     {
2545       return alreadyLoadedPDB.get(pdbId).toString();
2546     }
2547
2548     String tempFile = copyJarEntry(jprovider, pdbId, "jalview_pdb");
2549     if (tempFile != null)
2550     {
2551       alreadyLoadedPDB.put(pdbId, tempFile);
2552     }
2553     return tempFile;
2554   }
2555
2556   /**
2557    * Copies the jar entry of given name to a new temporary file and returns the
2558    * path to the file, or null if the entry is not found.
2559    * 
2560    * @param jprovider
2561    * @param jarEntryName
2562    * @param prefix
2563    *          a prefix for the temporary file name, must be at least three
2564    *          characters long
2565    * @return
2566    */
2567   protected String copyJarEntry(jarInputStreamProvider jprovider,
2568           String jarEntryName, String prefix)
2569   {
2570     BufferedReader in = null;
2571     PrintWriter out = null;
2572
2573     try
2574     {
2575       JarInputStream jin = jprovider.getJarInputStream();
2576       /*
2577        * if (jprovider.startsWith("http://")) { jin = new JarInputStream(new
2578        * URL(jprovider).openStream()); } else { jin = new JarInputStream(new
2579        * FileInputStream(jprovider)); }
2580        */
2581
2582       JarEntry entry = null;
2583       do
2584       {
2585         entry = jin.getNextJarEntry();
2586       } while (entry != null && !entry.getName().equals(jarEntryName));
2587       if (entry != null)
2588       {
2589         in = new BufferedReader(new InputStreamReader(jin, UTF_8));
2590         File outFile = File.createTempFile(prefix, ".tmp");
2591         outFile.deleteOnExit();
2592         out = new PrintWriter(new FileOutputStream(outFile));
2593         String data;
2594
2595         while ((data = in.readLine()) != null)
2596         {
2597           out.println(data);
2598         }
2599         out.flush();
2600         String t = outFile.getAbsolutePath();
2601         return t;
2602       }
2603       else
2604       {
2605         warn("Couldn't find entry in Jalview Jar for " + jarEntryName);
2606       }
2607     } catch (Exception ex)
2608     {
2609       ex.printStackTrace();
2610     } finally
2611     {
2612       if (in != null)
2613       {
2614         try
2615         {
2616           in.close();
2617         } catch (IOException e)
2618         {
2619           // ignore
2620         }
2621       }
2622       if (out != null)
2623       {
2624         out.close();
2625       }
2626     }
2627
2628     return null;
2629   }
2630
2631   private class JvAnnotRow
2632   {
2633     public JvAnnotRow(int i, AlignmentAnnotation jaa)
2634     {
2635       order = i;
2636       template = jaa;
2637     }
2638
2639     /**
2640      * persisted version of annotation row from which to take vis properties
2641      */
2642     public jalview.datamodel.AlignmentAnnotation template;
2643
2644     /**
2645      * original position of the annotation row in the alignment
2646      */
2647     public int order;
2648   }
2649
2650   /**
2651    * Load alignment frame from jalview XML DOM object
2652    * 
2653    * @param object
2654    *          DOM
2655    * @param file
2656    *          filename source string
2657    * @param loadTreesAndStructures
2658    *          when false only create Viewport
2659    * @param jprovider
2660    *          data source provider
2661    * @return alignment frame created from view stored in DOM
2662    */
2663   AlignFrame loadFromObject(JalviewModel object, String file,
2664           boolean loadTreesAndStructures, jarInputStreamProvider jprovider)
2665   {
2666     SequenceSet vamsasSet = object.getVamsasModel().getSequenceSet(0);
2667     Sequence[] vamsasSeq = vamsasSet.getSequence();
2668
2669     JalviewModelSequence jms = object.getJalviewModelSequence();
2670
2671     Viewport view = (jms.getViewportCount() > 0) ? jms.getViewport(0)
2672             : null;
2673
2674     // ////////////////////////////////
2675     // LOAD SEQUENCES
2676
2677     List<SequenceI> hiddenSeqs = null;
2678     jalview.datamodel.Sequence jseq;
2679
2680     List<SequenceI> tmpseqs = new ArrayList<SequenceI>();
2681
2682     boolean multipleView = false;
2683
2684     JSeq[] jseqs = object.getJalviewModelSequence().getJSeq();
2685     int vi = 0; // counter in vamsasSeq array
2686     for (int i = 0; i < jseqs.length; i++)
2687     {
2688       String seqId = jseqs[i].getId();
2689
2690       if (seqRefIds.get(seqId) != null)
2691       {
2692         tmpseqs.add(seqRefIds.get(seqId));
2693         multipleView = true;
2694       }
2695       else
2696       {
2697         jseq = new jalview.datamodel.Sequence(vamsasSeq[vi].getName(),
2698                 vamsasSeq[vi].getSequence());
2699         jseq.setDescription(vamsasSeq[vi].getDescription());
2700         jseq.setStart(jseqs[i].getStart());
2701         jseq.setEnd(jseqs[i].getEnd());
2702         jseq.setVamsasId(uniqueSetSuffix + seqId);
2703         seqRefIds.put(vamsasSeq[vi].getId(), jseq);
2704         tmpseqs.add(jseq);
2705         vi++;
2706       }
2707
2708       if (jseqs[i].getHidden())
2709       {
2710         if (hiddenSeqs == null)
2711         {
2712           hiddenSeqs = new ArrayList<SequenceI>();
2713         }
2714
2715         hiddenSeqs.add(seqRefIds.get(seqId));
2716       }
2717
2718     }
2719
2720     // /
2721     // Create the alignment object from the sequence set
2722     // ///////////////////////////////
2723     SequenceI[] orderedSeqs = tmpseqs
2724             .toArray(new SequenceI[tmpseqs.size()]);
2725
2726     Alignment al = new Alignment(orderedSeqs);
2727
2728     // / Add the alignment properties
2729     for (int i = 0; i < vamsasSet.getSequenceSetPropertiesCount(); i++)
2730     {
2731       SequenceSetProperties ssp = vamsasSet.getSequenceSetProperties(i);
2732       al.setProperty(ssp.getKey(), ssp.getValue());
2733     }
2734
2735     // /
2736     // SequenceFeatures are added to the DatasetSequence,
2737     // so we must create or recover the dataset before loading features
2738     // ///////////////////////////////
2739     if (vamsasSet.getDatasetId() == null || vamsasSet.getDatasetId() == "")
2740     {
2741       // older jalview projects do not have a dataset id.
2742       al.setDataset(null);
2743     }
2744     else
2745     {
2746       // recover dataset - passing on flag indicating if this a 'viewless'
2747       // sequence set (a.k.a. a stored dataset for the project)
2748       recoverDatasetFor(vamsasSet, al, object.getJalviewModelSequence()
2749               .getViewportCount() == 0);
2750     }
2751     // ///////////////////////////////
2752
2753     Hashtable pdbloaded = new Hashtable(); // TODO nothing writes to this??
2754     if (!multipleView)
2755     {
2756       // load sequence features, database references and any associated PDB
2757       // structures for the alignment
2758       for (int i = 0; i < vamsasSeq.length; i++)
2759       {
2760         if (jseqs[i].getFeaturesCount() > 0)
2761         {
2762           Features[] features = jseqs[i].getFeatures();
2763           for (int f = 0; f < features.length; f++)
2764           {
2765             jalview.datamodel.SequenceFeature sf = new jalview.datamodel.SequenceFeature(
2766                     features[f].getType(), features[f].getDescription(),
2767                     features[f].getStatus(), features[f].getBegin(),
2768                     features[f].getEnd(), features[f].getFeatureGroup());
2769
2770             sf.setScore(features[f].getScore());
2771             for (int od = 0; od < features[f].getOtherDataCount(); od++)
2772             {
2773               OtherData keyValue = features[f].getOtherData(od);
2774               if (keyValue.getKey().startsWith("LINK"))
2775               {
2776                 sf.addLink(keyValue.getValue());
2777               }
2778               else
2779               {
2780                 sf.setValue(keyValue.getKey(), keyValue.getValue());
2781               }
2782
2783             }
2784
2785             al.getSequenceAt(i).getDatasetSequence().addSequenceFeature(sf);
2786           }
2787         }
2788         if (vamsasSeq[i].getDBRefCount() > 0)
2789         {
2790           addDBRefs(al.getSequenceAt(i).getDatasetSequence(), vamsasSeq[i]);
2791         }
2792         if (jseqs[i].getPdbidsCount() > 0)
2793         {
2794           Pdbids[] ids = jseqs[i].getPdbids();
2795           for (int p = 0; p < ids.length; p++)
2796           {
2797             jalview.datamodel.PDBEntry entry = new jalview.datamodel.PDBEntry();
2798             entry.setId(ids[p].getId());
2799             if (ids[p].getType() != null)
2800             {
2801               if (ids[p].getType().equalsIgnoreCase("PDB"))
2802               {
2803                 entry.setType(PDBEntry.Type.PDB);
2804               }
2805               else
2806               {
2807                 entry.setType(PDBEntry.Type.FILE);
2808               }
2809             }
2810             if (ids[p].getFile() != null)
2811             {
2812               if (!pdbloaded.containsKey(ids[p].getFile()))
2813               {
2814                 entry.setFile(loadPDBFile(jprovider, ids[p].getId()));
2815               }
2816               else
2817               {
2818                 entry.setFile(pdbloaded.get(ids[p].getId()).toString());
2819               }
2820             }
2821             StructureSelectionManager.getStructureSelectionManager(
2822                     Desktop.instance).registerPDBEntry(entry);
2823             al.getSequenceAt(i).getDatasetSequence().addPDBId(entry);
2824           }
2825         }
2826       }
2827     } // end !multipleview
2828
2829     // ///////////////////////////////
2830     // LOAD SEQUENCE MAPPINGS
2831
2832     if (vamsasSet.getAlcodonFrameCount() > 0)
2833     {
2834       // TODO Potentially this should only be done once for all views of an
2835       // alignment
2836       AlcodonFrame[] alc = vamsasSet.getAlcodonFrame();
2837       for (int i = 0; i < alc.length; i++)
2838       {
2839         AlignedCodonFrame cf = new AlignedCodonFrame();
2840         if (alc[i].getAlcodMapCount() > 0)
2841         {
2842           AlcodMap[] maps = alc[i].getAlcodMap();
2843           for (int m = 0; m < maps.length; m++)
2844           {
2845             SequenceI dnaseq = seqRefIds.get(maps[m].getDnasq());
2846             // Load Mapping
2847             jalview.datamodel.Mapping mapping = null;
2848             // attach to dna sequence reference.
2849             if (maps[m].getMapping() != null)
2850             {
2851               mapping = addMapping(maps[m].getMapping());
2852             }
2853             if (dnaseq != null && mapping.getTo() != null)
2854             {
2855               cf.addMap(dnaseq, mapping.getTo(), mapping.getMap());
2856             }
2857             else
2858             {
2859               // defer to later
2860               frefedSequence.add(new Object[] { maps[m].getDnasq(), cf,
2861                   mapping });
2862             }
2863           }
2864         }
2865         al.addCodonFrame(cf);
2866       }
2867     }
2868
2869     // ////////////////////////////////
2870     // LOAD ANNOTATIONS
2871     List<JvAnnotRow> autoAlan = new ArrayList<JvAnnotRow>();
2872
2873     /*
2874      * store any annotations which forward reference a group's ID
2875      */
2876     Map<String, List<AlignmentAnnotation>> groupAnnotRefs = new Hashtable<String, List<AlignmentAnnotation>>();
2877
2878     if (vamsasSet.getAnnotationCount() > 0)
2879     {
2880       Annotation[] an = vamsasSet.getAnnotation();
2881
2882       for (int i = 0; i < an.length; i++)
2883       {
2884         Annotation annotation = an[i];
2885
2886         /**
2887          * test if annotation is automatically calculated for this view only
2888          */
2889         boolean autoForView = false;
2890         if (annotation.getLabel().equals("Quality")
2891                 || annotation.getLabel().equals("Conservation")
2892                 || annotation.getLabel().equals("Consensus"))
2893         {
2894           // Kludge for pre 2.5 projects which lacked the autocalculated flag
2895           autoForView = true;
2896           if (!annotation.hasAutoCalculated())
2897           {
2898             annotation.setAutoCalculated(true);
2899           }
2900         }
2901         if (autoForView
2902                 || (annotation.hasAutoCalculated() && annotation
2903                         .isAutoCalculated()))
2904         {
2905           // remove ID - we don't recover annotation from other views for
2906           // view-specific annotation
2907           annotation.setId(null);
2908         }
2909
2910         // set visiblity for other annotation in this view
2911         String annotationId = annotation.getId();
2912         if (annotationId != null && annotationIds.containsKey(annotationId))
2913         {
2914           AlignmentAnnotation jda = annotationIds.get(annotationId);
2915           // in principle Visible should always be true for annotation displayed
2916           // in multiple views
2917           if (annotation.hasVisible())
2918           {
2919             jda.visible = annotation.getVisible();
2920           }
2921
2922           al.addAnnotation(jda);
2923
2924           continue;
2925         }
2926         // Construct new annotation from model.
2927         AnnotationElement[] ae = annotation.getAnnotationElement();
2928         jalview.datamodel.Annotation[] anot = null;
2929         java.awt.Color firstColour = null;
2930         int anpos;
2931         if (!annotation.getScoreOnly())
2932         {
2933           anot = new jalview.datamodel.Annotation[al.getWidth()];
2934           for (int aa = 0; aa < ae.length && aa < anot.length; aa++)
2935           {
2936             anpos = ae[aa].getPosition();
2937
2938             if (anpos >= anot.length)
2939             {
2940               continue;
2941             }
2942
2943             anot[anpos] = new jalview.datamodel.Annotation(
2944
2945             ae[aa].getDisplayCharacter(), ae[aa].getDescription(),
2946                     (ae[aa].getSecondaryStructure() == null || ae[aa]
2947                             .getSecondaryStructure().length() == 0) ? ' '
2948                             : ae[aa].getSecondaryStructure().charAt(0),
2949                     ae[aa].getValue()
2950
2951             );
2952             // JBPNote: Consider verifying dataflow for IO of secondary
2953             // structure annotation read from Stockholm files
2954             // this was added to try to ensure that
2955             // if (anot[ae[aa].getPosition()].secondaryStructure>' ')
2956             // {
2957             // anot[ae[aa].getPosition()].displayCharacter = "";
2958             // }
2959             anot[anpos].colour = new java.awt.Color(ae[aa].getColour());
2960             if (firstColour == null)
2961             {
2962               firstColour = anot[anpos].colour;
2963             }
2964           }
2965         }
2966         jalview.datamodel.AlignmentAnnotation jaa = null;
2967
2968         if (annotation.getGraph())
2969         {
2970           float llim = 0, hlim = 0;
2971           // if (autoForView || an[i].isAutoCalculated()) {
2972           // hlim=11f;
2973           // }
2974           jaa = new jalview.datamodel.AlignmentAnnotation(
2975                   annotation.getLabel(), annotation.getDescription(), anot,
2976                   llim, hlim, annotation.getGraphType());
2977
2978           jaa.graphGroup = annotation.getGraphGroup();
2979           jaa._linecolour = firstColour;
2980           if (annotation.getThresholdLine() != null)
2981           {
2982             jaa.setThreshold(new jalview.datamodel.GraphLine(annotation
2983                     .getThresholdLine().getValue(), annotation
2984                     .getThresholdLine().getLabel(), new java.awt.Color(
2985                     annotation.getThresholdLine().getColour())));
2986
2987           }
2988           if (autoForView || annotation.isAutoCalculated())
2989           {
2990             // Hardwire the symbol display line to ensure that labels for
2991             // histograms are displayed
2992             jaa.hasText = true;
2993           }
2994         }
2995         else
2996         {
2997           jaa = new jalview.datamodel.AlignmentAnnotation(an[i].getLabel(),
2998                   an[i].getDescription(), anot);
2999           jaa._linecolour = firstColour;
3000         }
3001         // register new annotation
3002         if (an[i].getId() != null)
3003         {
3004           annotationIds.put(an[i].getId(), jaa);
3005           jaa.annotationId = an[i].getId();
3006         }
3007         // recover sequence association
3008         String sequenceRef = an[i].getSequenceRef();
3009         if (sequenceRef != null)
3010         {
3011           // from 2.9 sequenceRef is to sequence id (JAL-1781)
3012           SequenceI sequence = seqRefIds.get(sequenceRef);
3013           if (sequence == null)
3014           {
3015             // in pre-2.9 projects sequence ref is to sequence name
3016             sequence = al.findName(sequenceRef);
3017           }
3018           if (sequence != null)
3019           {
3020             jaa.createSequenceMapping(sequence, 1, true);
3021             sequence.addAlignmentAnnotation(jaa);
3022           }
3023         }
3024         // and make a note of any group association
3025         if (an[i].getGroupRef() != null && an[i].getGroupRef().length() > 0)
3026         {
3027           List<jalview.datamodel.AlignmentAnnotation> aal = groupAnnotRefs
3028                   .get(an[i].getGroupRef());
3029           if (aal == null)
3030           {
3031             aal = new ArrayList<jalview.datamodel.AlignmentAnnotation>();
3032             groupAnnotRefs.put(an[i].getGroupRef(), aal);
3033           }
3034           aal.add(jaa);
3035         }
3036
3037         if (an[i].hasScore())
3038         {
3039           jaa.setScore(an[i].getScore());
3040         }
3041         if (an[i].hasVisible())
3042         {
3043           jaa.visible = an[i].getVisible();
3044         }
3045
3046         if (an[i].hasCentreColLabels())
3047         {
3048           jaa.centreColLabels = an[i].getCentreColLabels();
3049         }
3050
3051         if (an[i].hasScaleColLabels())
3052         {
3053           jaa.scaleColLabel = an[i].getScaleColLabels();
3054         }
3055         if (an[i].hasAutoCalculated() && an[i].isAutoCalculated())
3056         {
3057           // newer files have an 'autoCalculated' flag and store calculation
3058           // state in viewport properties
3059           jaa.autoCalculated = true; // means annotation will be marked for
3060           // update at end of load.
3061         }
3062         if (an[i].hasGraphHeight())
3063         {
3064           jaa.graphHeight = an[i].getGraphHeight();
3065         }
3066         if (an[i].hasBelowAlignment())
3067         {
3068           jaa.belowAlignment = an[i].isBelowAlignment();
3069         }
3070         jaa.setCalcId(an[i].getCalcId());
3071         if (an[i].getPropertyCount() > 0)
3072         {
3073           for (jalview.schemabinding.version2.Property prop : an[i]
3074                   .getProperty())
3075           {
3076             jaa.setProperty(prop.getName(), prop.getValue());
3077           }
3078         }
3079         if (jaa.autoCalculated)
3080         {
3081           autoAlan.add(new JvAnnotRow(i, jaa));
3082         }
3083         else
3084         // if (!autoForView)
3085         {
3086           // add autocalculated group annotation and any user created annotation
3087           // for the view
3088           al.addAnnotation(jaa);
3089         }
3090       }
3091     }
3092     // ///////////////////////
3093     // LOAD GROUPS
3094     // Create alignment markup and styles for this view
3095     if (jms.getJGroupCount() > 0)
3096     {
3097       JGroup[] groups = jms.getJGroup();
3098       boolean addAnnotSchemeGroup = false;
3099       for (int i = 0; i < groups.length; i++)
3100       {
3101         JGroup jGroup = groups[i];
3102         ColourSchemeI cs = null;
3103         if (jGroup.getColour() != null)
3104         {
3105           if (jGroup.getColour().startsWith("ucs"))
3106           {
3107             cs = getUserColourScheme(jms, jGroup.getColour());
3108           }
3109           else if (jGroup.getColour().equals("AnnotationColourGradient")
3110                   && jGroup.getAnnotationColours() != null)
3111           {
3112             addAnnotSchemeGroup = true;
3113             cs = null;
3114           }
3115           else
3116           {
3117             cs = ColourSchemeProperty.getColour(al, jGroup.getColour());
3118           }
3119
3120           if (cs != null)
3121           {
3122             cs.setThreshold(jGroup.getPidThreshold(), true);
3123           }
3124         }
3125
3126         Vector<SequenceI> seqs = new Vector<SequenceI>();
3127
3128         for (int s = 0; s < jGroup.getSeqCount(); s++)
3129         {
3130           String seqId = jGroup.getSeq(s) + "";
3131           SequenceI ts = seqRefIds.get(seqId);
3132
3133           if (ts != null)
3134           {
3135             seqs.addElement(ts);
3136           }
3137         }
3138
3139         if (seqs.size() < 1)
3140         {
3141           continue;
3142         }
3143
3144         SequenceGroup sg = new SequenceGroup(seqs, jGroup.getName(), cs,
3145                 jGroup.getDisplayBoxes(), jGroup.getDisplayText(),
3146                 jGroup.getColourText(), jGroup.getStart(), jGroup.getEnd());
3147
3148         sg.setOutlineColour(new java.awt.Color(jGroup.getOutlineColour()));
3149
3150         sg.textColour = new java.awt.Color(jGroup.getTextCol1());
3151         sg.textColour2 = new java.awt.Color(jGroup.getTextCol2());
3152         sg.setShowNonconserved(jGroup.hasShowUnconserved() ? jGroup
3153                 .isShowUnconserved() : false);
3154         sg.thresholdTextColour = jGroup.getTextColThreshold();
3155         if (jGroup.hasShowConsensusHistogram())
3156         {
3157           sg.setShowConsensusHistogram(jGroup.isShowConsensusHistogram());
3158         }
3159         ;
3160         if (jGroup.hasShowSequenceLogo())
3161         {
3162           sg.setshowSequenceLogo(jGroup.isShowSequenceLogo());
3163         }
3164         if (jGroup.hasNormaliseSequenceLogo())
3165         {
3166           sg.setNormaliseSequenceLogo(jGroup.isNormaliseSequenceLogo());
3167         }
3168         if (jGroup.hasIgnoreGapsinConsensus())
3169         {
3170           sg.setIgnoreGapsConsensus(jGroup.getIgnoreGapsinConsensus());
3171         }
3172         if (jGroup.getConsThreshold() != 0)
3173         {
3174           jalview.analysis.Conservation c = new jalview.analysis.Conservation(
3175                   "All", ResidueProperties.propHash, 3,
3176                   sg.getSequences(null), 0, sg.getWidth() - 1);
3177           c.calculate();
3178           c.verdict(false, 25);
3179           sg.cs.setConservation(c);
3180         }
3181
3182         if (jGroup.getId() != null && groupAnnotRefs.size() > 0)
3183         {
3184           // re-instate unique group/annotation row reference
3185           List<AlignmentAnnotation> jaal = groupAnnotRefs.get(jGroup
3186                   .getId());
3187           if (jaal != null)
3188           {
3189             for (AlignmentAnnotation jaa : jaal)
3190             {
3191               jaa.groupRef = sg;
3192               if (jaa.autoCalculated)
3193               {
3194                 // match up and try to set group autocalc alignment row for this
3195                 // annotation
3196                 if (jaa.label.startsWith("Consensus for "))
3197                 {
3198                   sg.setConsensus(jaa);
3199                 }
3200                 // match up and try to set group autocalc alignment row for this
3201                 // annotation
3202                 if (jaa.label.startsWith("Conservation for "))
3203                 {
3204                   sg.setConservationRow(jaa);
3205                 }
3206               }
3207             }
3208           }
3209         }
3210         al.addGroup(sg);
3211         if (addAnnotSchemeGroup)
3212         {
3213           // reconstruct the annotation colourscheme
3214           sg.cs = constructAnnotationColour(jGroup.getAnnotationColours(),
3215                   null, al, jms, false);
3216         }
3217       }
3218     }
3219     if (view == null)
3220     {
3221       // only dataset in this model, so just return.
3222       return null;
3223     }
3224     // ///////////////////////////////
3225     // LOAD VIEWPORT
3226
3227     // If we just load in the same jar file again, the sequenceSetId
3228     // will be the same, and we end up with multiple references
3229     // to the same sequenceSet. We must modify this id on load
3230     // so that each load of the file gives a unique id
3231     String uniqueSeqSetId = view.getSequenceSetId() + uniqueSetSuffix;
3232     String viewId = (view.getId() == null ? null : view.getId()
3233             + uniqueSetSuffix);
3234     AlignFrame af = null;
3235     AlignViewport av = null;
3236     // now check to see if we really need to create a new viewport.
3237     if (multipleView && viewportsAdded.size() == 0)
3238     {
3239       // We recovered an alignment for which a viewport already exists.
3240       // TODO: fix up any settings necessary for overlaying stored state onto
3241       // state recovered from another document. (may not be necessary).
3242       // we may need a binding from a viewport in memory to one recovered from
3243       // XML.
3244       // and then recover its containing af to allow the settings to be applied.
3245       // TODO: fix for vamsas demo
3246       System.err
3247               .println("About to recover a viewport for existing alignment: Sequence set ID is "
3248                       + uniqueSeqSetId);
3249       Object seqsetobj = retrieveExistingObj(uniqueSeqSetId);
3250       if (seqsetobj != null)
3251       {
3252         if (seqsetobj instanceof String)
3253         {
3254           uniqueSeqSetId = (String) seqsetobj;
3255           System.err
3256                   .println("Recovered extant sequence set ID mapping for ID : New Sequence set ID is "
3257                           + uniqueSeqSetId);
3258         }
3259         else
3260         {
3261           System.err
3262                   .println("Warning : Collision between sequence set ID string and existing jalview object mapping.");
3263         }
3264
3265       }
3266     }
3267     /**
3268      * indicate that annotation colours are applied across all groups (pre
3269      * Jalview 2.8.1 behaviour)
3270      */
3271     boolean doGroupAnnColour = Jalview2XML.isVersionStringLaterThan(
3272             "2.8.1", object.getVersion());
3273
3274     AlignmentPanel ap = null;
3275     boolean isnewview = true;
3276     if (viewId != null)
3277     {
3278       // Check to see if this alignment already has a view id == viewId
3279       jalview.gui.AlignmentPanel views[] = Desktop
3280               .getAlignmentPanels(uniqueSeqSetId);
3281       if (views != null && views.length > 0)
3282       {
3283         for (int v = 0; v < views.length; v++)
3284         {
3285           if (views[v].av.getViewId().equalsIgnoreCase(viewId))
3286           {
3287             // recover the existing alignpanel, alignframe, viewport
3288             af = views[v].alignFrame;
3289             av = views[v].av;
3290             ap = views[v];
3291             // TODO: could even skip resetting view settings if we don't want to
3292             // change the local settings from other jalview processes
3293             isnewview = false;
3294           }
3295         }
3296       }
3297     }
3298
3299     if (isnewview)
3300     {
3301       af = loadViewport(file, jseqs, hiddenSeqs, al, jms, view,
3302               uniqueSeqSetId, viewId, autoAlan);
3303       av = af.viewport;
3304       ap = af.alignPanel;
3305     }
3306
3307     /*
3308      * Load any trees, PDB structures and viewers
3309      * 
3310      * Not done if flag is false (when this method is used for New View)
3311      */
3312     if (loadTreesAndStructures)
3313     {
3314       loadTrees(jms, view, af, av, ap);
3315       loadPDBStructures(jprovider, jseqs, af, ap);
3316       loadRnaViewers(jprovider, jseqs, ap);
3317     }
3318     // and finally return.
3319     return af;
3320   }
3321
3322   /**
3323    * Instantiate and link any saved RNA (Varna) viewers. The state of the Varna
3324    * panel is restored from separate jar entries, two (gapped and trimmed) per
3325    * sequence and secondary structure.
3326    * 
3327    * Currently each viewer shows just one sequence and structure (gapped and
3328    * trimmed), however this method is designed to support multiple sequences or
3329    * structures in viewers if wanted in future.
3330    * 
3331    * @param jprovider
3332    * @param jseqs
3333    * @param ap
3334    */
3335   private void loadRnaViewers(jarInputStreamProvider jprovider,
3336           JSeq[] jseqs, AlignmentPanel ap)
3337   {
3338     /*
3339      * scan the sequences for references to viewers; create each one the first
3340      * time it is referenced, add Rna models to existing viewers
3341      */
3342     for (JSeq jseq : jseqs)
3343     {
3344       for (int i = 0; i < jseq.getRnaViewerCount(); i++)
3345       {
3346         RnaViewer viewer = jseq.getRnaViewer(i);
3347         AppVarna appVarna = findOrCreateVarnaViewer(viewer,
3348                 uniqueSetSuffix, ap);
3349
3350         for (int j = 0; j < viewer.getSecondaryStructureCount(); j++)
3351         {
3352           SecondaryStructure ss = viewer.getSecondaryStructure(j);
3353           SequenceI seq = seqRefIds.get(jseq.getId());
3354           AlignmentAnnotation ann = this.annotationIds.get(ss
3355                   .getAnnotationId());
3356
3357           /*
3358            * add the structure to the Varna display (with session state copied
3359            * from the jar to a temporary file)
3360            */
3361           boolean gapped = ss.isGapped();
3362           String rnaTitle = ss.getTitle();
3363           String sessionState = ss.getViewerState();
3364           String tempStateFile = copyJarEntry(jprovider, sessionState,
3365                   "varna");
3366           RnaModel rna = new RnaModel(rnaTitle, ann, seq, null, gapped);
3367           appVarna.addModelSession(rna, rnaTitle, tempStateFile);
3368         }
3369         appVarna.setInitialSelection(viewer.getSelectedRna());
3370       }
3371     }
3372   }
3373
3374   /**
3375    * Locate and return an already instantiated matching AppVarna, or create one
3376    * if not found
3377    * 
3378    * @param viewer
3379    * @param viewIdSuffix
3380    * @param ap
3381    * @return
3382    */
3383   protected AppVarna findOrCreateVarnaViewer(RnaViewer viewer,
3384           String viewIdSuffix, AlignmentPanel ap)
3385   {
3386     /*
3387      * on each load a suffix is appended to the saved viewId, to avoid conflicts
3388      * if load is repeated
3389      */
3390     String postLoadId = viewer.getViewId() + viewIdSuffix;
3391     for (JInternalFrame frame : getAllFrames())
3392     {
3393       if (frame instanceof AppVarna)
3394       {
3395         AppVarna varna = (AppVarna) frame;
3396         if (postLoadId.equals(varna.getViewId()))
3397         {
3398           // this viewer is already instantiated
3399           // could in future here add ap as another 'parent' of the
3400           // AppVarna window; currently just 1-to-many
3401           return varna;
3402         }
3403       }
3404     }
3405
3406     /*
3407      * viewer not found - make it
3408      */
3409     RnaViewerModel model = new RnaViewerModel(postLoadId,
3410             viewer.getTitle(), viewer.getXpos(), viewer.getYpos(),
3411             viewer.getWidth(), viewer.getHeight(),
3412             viewer.getDividerLocation());
3413     AppVarna varna = new AppVarna(model, ap);
3414
3415     return varna;
3416   }
3417
3418   /**
3419    * Load any saved trees
3420    * 
3421    * @param jms
3422    * @param view
3423    * @param af
3424    * @param av
3425    * @param ap
3426    */
3427   protected void loadTrees(JalviewModelSequence jms, Viewport view,
3428           AlignFrame af, AlignViewport av, AlignmentPanel ap)
3429   {
3430     // TODO result of automated refactoring - are all these parameters needed?
3431     try
3432     {
3433       for (int t = 0; t < jms.getTreeCount(); t++)
3434       {
3435
3436         Tree tree = jms.getTree(t);
3437
3438         TreePanel tp = (TreePanel) retrieveExistingObj(tree.getId());
3439         if (tp == null)
3440         {
3441           tp = af.ShowNewickTree(
3442                   new jalview.io.NewickFile(tree.getNewick()),
3443                   tree.getTitle(), tree.getWidth(), tree.getHeight(),
3444                   tree.getXpos(), tree.getYpos());
3445           if (tree.getId() != null)
3446           {
3447             // perhaps bind the tree id to something ?
3448           }
3449         }
3450         else
3451         {
3452           // update local tree attributes ?
3453           // TODO: should check if tp has been manipulated by user - if so its
3454           // settings shouldn't be modified
3455           tp.setTitle(tree.getTitle());
3456           tp.setBounds(new Rectangle(tree.getXpos(), tree.getYpos(), tree
3457                   .getWidth(), tree.getHeight()));
3458           tp.av = av; // af.viewport; // TODO: verify 'associate with all
3459           // views'
3460           // works still
3461           tp.treeCanvas.av = av; // af.viewport;
3462           tp.treeCanvas.ap = ap; // af.alignPanel;
3463
3464         }
3465         if (tp == null)
3466         {
3467           warn("There was a problem recovering stored Newick tree: \n"
3468                   + tree.getNewick());
3469           continue;
3470         }
3471
3472         tp.fitToWindow.setState(tree.getFitToWindow());
3473         tp.fitToWindow_actionPerformed(null);
3474
3475         if (tree.getFontName() != null)
3476         {
3477           tp.setTreeFont(new java.awt.Font(tree.getFontName(), tree
3478                   .getFontStyle(), tree.getFontSize()));
3479         }
3480         else
3481         {
3482           tp.setTreeFont(new java.awt.Font(view.getFontName(), view
3483                   .getFontStyle(), tree.getFontSize()));
3484         }
3485
3486         tp.showPlaceholders(tree.getMarkUnlinked());
3487         tp.showBootstrap(tree.getShowBootstrap());
3488         tp.showDistances(tree.getShowDistances());
3489
3490         tp.treeCanvas.threshold = tree.getThreshold();
3491
3492         if (tree.getCurrentTree())
3493         {
3494           af.viewport.setCurrentTree(tp.getTree());
3495         }
3496       }
3497
3498     } catch (Exception ex)
3499     {
3500       ex.printStackTrace();
3501     }
3502   }
3503
3504   /**
3505    * Load and link any saved structure viewers.
3506    * 
3507    * @param jprovider
3508    * @param jseqs
3509    * @param af
3510    * @param ap
3511    */
3512   protected void loadPDBStructures(jarInputStreamProvider jprovider,
3513           JSeq[] jseqs, AlignFrame af, AlignmentPanel ap)
3514   {
3515     /*
3516      * Run through all PDB ids on the alignment, and collect mappings between
3517      * distinct view ids and all sequences referring to that view.
3518      */
3519     Map<String, StructureViewerModel> structureViewers = new LinkedHashMap<String, StructureViewerModel>();
3520
3521     for (int i = 0; i < jseqs.length; i++)
3522     {
3523       if (jseqs[i].getPdbidsCount() > 0)
3524       {
3525         Pdbids[] ids = jseqs[i].getPdbids();
3526         for (int p = 0; p < ids.length; p++)
3527         {
3528           final int structureStateCount = ids[p].getStructureStateCount();
3529           for (int s = 0; s < structureStateCount; s++)
3530           {
3531             // check to see if we haven't already created this structure view
3532             final StructureState structureState = ids[p]
3533                     .getStructureState(s);
3534             String sviewid = (structureState.getViewId() == null) ? null
3535                     : structureState.getViewId() + uniqueSetSuffix;
3536             jalview.datamodel.PDBEntry jpdb = new jalview.datamodel.PDBEntry();
3537             // Originally : ids[p].getFile()
3538             // : TODO: verify external PDB file recovery still works in normal
3539             // jalview project load
3540             jpdb.setFile(loadPDBFile(jprovider, ids[p].getId()));
3541             jpdb.setId(ids[p].getId());
3542
3543             int x = structureState.getXpos();
3544             int y = structureState.getYpos();
3545             int width = structureState.getWidth();
3546             int height = structureState.getHeight();
3547
3548             // Probably don't need to do this anymore...
3549             // Desktop.desktop.getComponentAt(x, y);
3550             // TODO: NOW: check that this recovers the PDB file correctly.
3551             String pdbFile = loadPDBFile(jprovider, ids[p].getId());
3552             jalview.datamodel.SequenceI seq = seqRefIds.get(jseqs[i]
3553                     .getId() + "");
3554             if (sviewid == null)
3555             {
3556               sviewid = "_jalview_pre2_4_" + x + "," + y + "," + width
3557                       + "," + height;
3558             }
3559             if (!structureViewers.containsKey(sviewid))
3560             {
3561               structureViewers.put(sviewid,
3562                       new StructureViewerModel(x, y, width, height, false,
3563                               false, true, structureState.getViewId(),
3564                               structureState.getType()));
3565               // Legacy pre-2.7 conversion JAL-823 :
3566               // do not assume any view has to be linked for colour by
3567               // sequence
3568             }
3569
3570             // assemble String[] { pdb files }, String[] { id for each
3571             // file }, orig_fileloc, SequenceI[][] {{ seqs_file 1 }, {
3572             // seqs_file 2}, boolean[] {
3573             // linkAlignPanel,superposeWithAlignpanel}} from hash
3574             StructureViewerModel jmoldat = structureViewers.get(sviewid);
3575             jmoldat.setAlignWithPanel(jmoldat.isAlignWithPanel()
3576                     | (structureState.hasAlignwithAlignPanel() ? structureState
3577                             .getAlignwithAlignPanel() : false));
3578
3579             /*
3580              * Default colour by linked panel to false if not specified (e.g.
3581              * for pre-2.7 projects)
3582              */
3583             boolean colourWithAlignPanel = jmoldat.isColourWithAlignPanel();
3584             colourWithAlignPanel |= (structureState
3585                     .hasColourwithAlignPanel() ? structureState
3586                     .getColourwithAlignPanel() : false);
3587             jmoldat.setColourWithAlignPanel(colourWithAlignPanel);
3588
3589             /*
3590              * Default colour by viewer to true if not specified (e.g. for
3591              * pre-2.7 projects)
3592              */
3593             boolean colourByViewer = jmoldat.isColourByViewer();
3594             colourByViewer &= structureState.hasColourByJmol() ? structureState
3595                     .getColourByJmol() : true;
3596             jmoldat.setColourByViewer(colourByViewer);
3597
3598             if (jmoldat.getStateData().length() < structureState
3599                     .getContent().length())
3600             {
3601               {
3602                 jmoldat.setStateData(structureState.getContent());
3603               }
3604             }
3605             if (ids[p].getFile() != null)
3606             {
3607               File mapkey = new File(ids[p].getFile());
3608               StructureData seqstrmaps = jmoldat.getFileData().get(mapkey);
3609               if (seqstrmaps == null)
3610               {
3611                 jmoldat.getFileData().put(
3612                         mapkey,
3613                         seqstrmaps = jmoldat.new StructureData(pdbFile,
3614                                 ids[p].getId()));
3615               }
3616               if (!seqstrmaps.getSeqList().contains(seq))
3617               {
3618                 seqstrmaps.getSeqList().add(seq);
3619                 // TODO and chains?
3620               }
3621             }
3622             else
3623             {
3624               errorMessage = ("The Jmol views in this project were imported\nfrom an older version of Jalview.\nPlease review the sequence colour associations\nin the Colour by section of the Jmol View menu.\n\nIn the case of problems, see note at\nhttp://issues.jalview.org/browse/JAL-747");
3625               warn(errorMessage);
3626             }
3627           }
3628         }
3629       }
3630     }
3631     // Instantiate the associated structure views
3632     for (Entry<String, StructureViewerModel> entry : structureViewers
3633             .entrySet())
3634     {
3635       try
3636       {
3637         createOrLinkStructureViewer(entry, af, ap, jprovider);
3638       } catch (Exception e)
3639       {
3640         System.err.println("Error loading structure viewer: "
3641                 + e.getMessage());
3642         // failed - try the next one
3643       }
3644     }
3645   }
3646
3647   /**
3648    * 
3649    * @param viewerData
3650    * @param af
3651    * @param ap
3652    * @param jprovider
3653    */
3654   protected void createOrLinkStructureViewer(
3655           Entry<String, StructureViewerModel> viewerData, AlignFrame af,
3656           AlignmentPanel ap, jarInputStreamProvider jprovider)
3657   {
3658     final StructureViewerModel stateData = viewerData.getValue();
3659
3660     /*
3661      * Search for any viewer windows already open from other alignment views
3662      * that exactly match the stored structure state
3663      */
3664     StructureViewerBase comp = findMatchingViewer(viewerData);
3665
3666     if (comp != null)
3667     {
3668       linkStructureViewer(ap, comp, stateData);
3669       return;
3670     }
3671
3672     /*
3673      * From 2.9: stateData.type contains JMOL or CHIMERA, data is in jar entry
3674      * "viewer_"+stateData.viewId
3675      */
3676     if (ViewerType.CHIMERA.toString().equals(stateData.getType()))
3677     {
3678       createChimeraViewer(viewerData, af, jprovider);
3679     }
3680     else
3681     {
3682       /*
3683        * else Jmol (if pre-2.9, stateData contains JMOL state string)
3684        */
3685       createJmolViewer(viewerData, af, jprovider);
3686     }
3687   }
3688
3689   /**
3690    * Create a new Chimera viewer.
3691    * 
3692    * @param data
3693    * @param af
3694    * @param jprovider
3695    */
3696   protected void createChimeraViewer(
3697           Entry<String, StructureViewerModel> viewerData, AlignFrame af,
3698           jarInputStreamProvider jprovider)
3699   {
3700     StructureViewerModel data = viewerData.getValue();
3701     String chimeraSessionFile = data.getStateData();
3702
3703     /*
3704      * Copy Chimera session from jar entry "viewer_"+viewId to a temporary file
3705      * 
3706      * NB this is the 'saved' viewId as in the project file XML, _not_ the
3707      * 'uniquified' sviewid used to reconstruct the viewer here
3708      */
3709     String viewerJarEntryName = getViewerJarEntryName(data.getViewId());
3710     chimeraSessionFile = copyJarEntry(jprovider, viewerJarEntryName,
3711             "chimera");
3712
3713     Set<Entry<File, StructureData>> fileData = data.getFileData()
3714             .entrySet();
3715     List<PDBEntry> pdbs = new ArrayList<PDBEntry>();
3716     List<SequenceI[]> allseqs = new ArrayList<SequenceI[]>();
3717     for (Entry<File, StructureData> pdb : fileData)
3718     {
3719       String filePath = pdb.getValue().getFilePath();
3720       String pdbId = pdb.getValue().getPdbId();
3721       // pdbs.add(new PDBEntry(filePath, pdbId));
3722       pdbs.add(new PDBEntry(pdbId, null, PDBEntry.Type.PDB, filePath));
3723       final List<SequenceI> seqList = pdb.getValue().getSeqList();
3724       SequenceI[] seqs = seqList.toArray(new SequenceI[seqList.size()]);
3725       allseqs.add(seqs);
3726     }
3727
3728     boolean colourByChimera = data.isColourByViewer();
3729     boolean colourBySequence = data.isColourWithAlignPanel();
3730
3731     // TODO use StructureViewer as a factory here, see JAL-1761
3732     final PDBEntry[] pdbArray = pdbs.toArray(new PDBEntry[pdbs.size()]);
3733     final SequenceI[][] seqsArray = allseqs.toArray(new SequenceI[allseqs
3734             .size()][]);
3735     String newViewId = viewerData.getKey();
3736
3737     ChimeraViewFrame cvf = new ChimeraViewFrame(chimeraSessionFile,
3738             af.alignPanel, pdbArray, seqsArray, colourByChimera,
3739             colourBySequence, newViewId);
3740     cvf.setSize(data.getWidth(), data.getHeight());
3741     cvf.setLocation(data.getX(), data.getY());
3742   }
3743
3744   /**
3745    * Create a new Jmol window. First parse the Jmol state to translate filenames
3746    * loaded into the view, and record the order in which files are shown in the
3747    * Jmol view, so we can add the sequence mappings in same order.
3748    * 
3749    * @param viewerData
3750    * @param af
3751    * @param jprovider
3752    */
3753   protected void createJmolViewer(
3754           final Entry<String, StructureViewerModel> viewerData,
3755           AlignFrame af, jarInputStreamProvider jprovider)
3756   {
3757     final StructureViewerModel svattrib = viewerData.getValue();
3758     String state = svattrib.getStateData();
3759
3760     /*
3761      * Pre-2.9: state element value is the Jmol state string
3762      * 
3763      * 2.9+: @type is "JMOL", state data is in a Jar file member named "viewer_"
3764      * + viewId
3765      */
3766     if (ViewerType.JMOL.toString().equals(svattrib.getType()))
3767     {
3768       state = readJarEntry(jprovider,
3769               getViewerJarEntryName(svattrib.getViewId()));
3770     }
3771
3772     List<String> pdbfilenames = new ArrayList<String>();
3773     List<SequenceI[]> seqmaps = new ArrayList<SequenceI[]>();
3774     List<String> pdbids = new ArrayList<String>();
3775     StringBuilder newFileLoc = new StringBuilder(64);
3776     int cp = 0, ncp, ecp;
3777     Map<File, StructureData> oldFiles = svattrib.getFileData();
3778     while ((ncp = state.indexOf("load ", cp)) > -1)
3779     {
3780       do
3781       {
3782         // look for next filename in load statement
3783         newFileLoc.append(state.substring(cp,
3784                 ncp = (state.indexOf("\"", ncp + 1) + 1)));
3785         String oldfilenam = state.substring(ncp,
3786                 ecp = state.indexOf("\"", ncp));
3787         // recover the new mapping data for this old filename
3788         // have to normalize filename - since Jmol and jalview do
3789         // filename
3790         // translation differently.
3791         StructureData filedat = oldFiles.get(new File(oldfilenam));
3792         newFileLoc.append(Platform.escapeString(filedat.getFilePath()));
3793         pdbfilenames.add(filedat.getFilePath());
3794         pdbids.add(filedat.getPdbId());
3795         seqmaps.add(filedat.getSeqList().toArray(new SequenceI[0]));
3796         newFileLoc.append("\"");
3797         cp = ecp + 1; // advance beyond last \" and set cursor so we can
3798                       // look for next file statement.
3799       } while ((ncp = state.indexOf("/*file*/", cp)) > -1);
3800     }
3801     if (cp > 0)
3802     {
3803       // just append rest of state
3804       newFileLoc.append(state.substring(cp));
3805     }
3806     else
3807     {
3808       System.err.print("Ignoring incomplete Jmol state for PDB ids: ");
3809       newFileLoc = new StringBuilder(state);
3810       newFileLoc.append("; load append ");
3811       for (File id : oldFiles.keySet())
3812       {
3813         // add this and any other pdb files that should be present in
3814         // the viewer
3815         StructureData filedat = oldFiles.get(id);
3816         newFileLoc.append(filedat.getFilePath());
3817         pdbfilenames.add(filedat.getFilePath());
3818         pdbids.add(filedat.getPdbId());
3819         seqmaps.add(filedat.getSeqList().toArray(new SequenceI[0]));
3820         newFileLoc.append(" \"");
3821         newFileLoc.append(filedat.getFilePath());
3822         newFileLoc.append("\"");
3823
3824       }
3825       newFileLoc.append(";");
3826     }
3827
3828     if (newFileLoc.length() == 0)
3829     {
3830       return;
3831     }
3832     int histbug = newFileLoc.indexOf("history = ");
3833     if (histbug > -1)
3834     {
3835       /*
3836        * change "history = [true|false];" to "history = [1|0];"
3837        */
3838       histbug += 10;
3839       int diff = histbug == -1 ? -1 : newFileLoc.indexOf(";", histbug);
3840       String val = (diff == -1) ? null : newFileLoc
3841               .substring(histbug, diff);
3842       if (val != null && val.length() >= 4)
3843       {
3844         if (val.contains("e")) // eh? what can it be?
3845         {
3846           if (val.trim().equals("true"))
3847           {
3848             val = "1";
3849           }
3850           else
3851           {
3852             val = "0";
3853           }
3854           newFileLoc.replace(histbug, diff, val);
3855         }
3856       }
3857     }
3858
3859     final String[] pdbf = pdbfilenames.toArray(new String[pdbfilenames
3860             .size()]);
3861     final String[] id = pdbids.toArray(new String[pdbids.size()]);
3862     final SequenceI[][] sq = seqmaps
3863             .toArray(new SequenceI[seqmaps.size()][]);
3864     final String fileloc = newFileLoc.toString();
3865     final String sviewid = viewerData.getKey();
3866     final AlignFrame alf = af;
3867     final Rectangle rect = new Rectangle(svattrib.getX(), svattrib.getY(),
3868             svattrib.getWidth(), svattrib.getHeight());
3869     try
3870     {
3871       javax.swing.SwingUtilities.invokeAndWait(new Runnable()
3872       {
3873         @Override
3874         public void run()
3875         {
3876           JalviewStructureDisplayI sview = null;
3877           try
3878           {
3879             sview = new StructureViewer(alf.alignPanel
3880                     .getStructureSelectionManager()).createView(
3881                     StructureViewer.ViewerType.JMOL, pdbf, id, sq,
3882                     alf.alignPanel, svattrib, fileloc, rect, sviewid);
3883             addNewStructureViewer(sview);
3884           } catch (OutOfMemoryError ex)
3885           {
3886             new OOMWarning("restoring structure view for PDB id " + id,
3887                     (OutOfMemoryError) ex.getCause());
3888             if (sview != null && sview.isVisible())
3889             {
3890               sview.closeViewer(false);
3891               sview.setVisible(false);
3892               sview.dispose();
3893             }
3894           }
3895         }
3896       });
3897     } catch (InvocationTargetException ex)
3898     {
3899       warn("Unexpected error when opening Jmol view.", ex);
3900
3901     } catch (InterruptedException e)
3902     {
3903       // e.printStackTrace();
3904     }
3905
3906   }
3907
3908   /**
3909    * Generates a name for the entry in the project jar file to hold state
3910    * information for a structure viewer
3911    * 
3912    * @param viewId
3913    * @return
3914    */
3915   protected String getViewerJarEntryName(String viewId)
3916   {
3917     return VIEWER_PREFIX + viewId;
3918   }
3919
3920   /**
3921    * Returns any open frame that matches given structure viewer data. The match
3922    * is based on the unique viewId, or (for older project versions) the frame's
3923    * geometry.
3924    * 
3925    * @param viewerData
3926    * @return
3927    */
3928   protected StructureViewerBase findMatchingViewer(
3929           Entry<String, StructureViewerModel> viewerData)
3930   {
3931     final String sviewid = viewerData.getKey();
3932     final StructureViewerModel svattrib = viewerData.getValue();
3933     StructureViewerBase comp = null;
3934     JInternalFrame[] frames = getAllFrames();
3935     for (JInternalFrame frame : frames)
3936     {
3937       if (frame instanceof StructureViewerBase)
3938       {
3939         /*
3940          * Post jalview 2.4 schema includes structure view id
3941          */
3942         if (sviewid != null
3943                 && ((StructureViewerBase) frame).getViewId()
3944                         .equals(sviewid))
3945         {
3946           comp = (StructureViewerBase) frame;
3947           break; // break added in 2.9
3948         }
3949         /*
3950          * Otherwise test for matching position and size of viewer frame
3951          */
3952         else if (frame.getX() == svattrib.getX()
3953                 && frame.getY() == svattrib.getY()
3954                 && frame.getHeight() == svattrib.getHeight()
3955                 && frame.getWidth() == svattrib.getWidth())
3956         {
3957           comp = (StructureViewerBase) frame;
3958           // no break in faint hope of an exact match on viewId
3959         }
3960       }
3961     }
3962     return comp;
3963   }
3964
3965   /**
3966    * Link an AlignmentPanel to an existing structure viewer.
3967    * 
3968    * @param ap
3969    * @param viewer
3970    * @param oldFiles
3971    * @param useinViewerSuperpos
3972    * @param usetoColourbyseq
3973    * @param viewerColouring
3974    */
3975   protected void linkStructureViewer(AlignmentPanel ap,
3976           StructureViewerBase viewer, StructureViewerModel stateData)
3977   {
3978     // NOTE: if the jalview project is part of a shared session then
3979     // view synchronization should/could be done here.
3980
3981     final boolean useinViewerSuperpos = stateData.isAlignWithPanel();
3982     final boolean usetoColourbyseq = stateData.isColourWithAlignPanel();
3983     final boolean viewerColouring = stateData.isColourByViewer();
3984     Map<File, StructureData> oldFiles = stateData.getFileData();
3985
3986     /*
3987      * Add mapping for sequences in this view to an already open viewer
3988      */
3989     final AAStructureBindingModel binding = viewer.getBinding();
3990     for (File id : oldFiles.keySet())
3991     {
3992       // add this and any other pdb files that should be present in the
3993       // viewer
3994       StructureData filedat = oldFiles.get(id);
3995       String pdbFile = filedat.getFilePath();
3996       SequenceI[] seq = filedat.getSeqList().toArray(new SequenceI[0]);
3997       binding.getSsm().setMapping(seq, null, pdbFile,
3998               jalview.io.AppletFormatAdapter.FILE);
3999       binding.addSequenceForStructFile(pdbFile, seq);
4000     }
4001     // and add the AlignmentPanel's reference to the view panel
4002     viewer.addAlignmentPanel(ap);
4003     if (useinViewerSuperpos)
4004     {
4005       viewer.useAlignmentPanelForSuperposition(ap);
4006     }
4007     else
4008     {
4009       viewer.excludeAlignmentPanelForSuperposition(ap);
4010     }
4011     if (usetoColourbyseq)
4012     {
4013       viewer.useAlignmentPanelForColourbyseq(ap, !viewerColouring);
4014     }
4015     else
4016     {
4017       viewer.excludeAlignmentPanelForColourbyseq(ap);
4018     }
4019   }
4020
4021   /**
4022    * Get all frames within the Desktop.
4023    * 
4024    * @return
4025    */
4026   protected JInternalFrame[] getAllFrames()
4027   {
4028     JInternalFrame[] frames = null;
4029     // TODO is this necessary - is it safe - risk of hanging?
4030     do
4031     {
4032       try
4033       {
4034         frames = Desktop.desktop.getAllFrames();
4035       } catch (ArrayIndexOutOfBoundsException e)
4036       {
4037         // occasional No such child exceptions are thrown here...
4038         try
4039         {
4040           Thread.sleep(10);
4041         } catch (InterruptedException f)
4042         {
4043         }
4044       }
4045     } while (frames == null);
4046     return frames;
4047   }
4048
4049   /**
4050    * 
4051    * @param supported
4052    *          - minimum version we are comparing against
4053    * @param version
4054    *          - version of data being processsed.
4055    * @return true if version is development/null or evaluates to the same or
4056    *         later X.Y.Z (where X,Y,Z are like [0-9]+b?[0-9]*)
4057    */
4058   public static boolean isVersionStringLaterThan(String supported,
4059           String version)
4060   {
4061     if (version == null || version.equalsIgnoreCase("DEVELOPMENT BUILD")
4062             || version.equalsIgnoreCase("Test")
4063             || version.equalsIgnoreCase("AUTOMATED BUILD"))
4064     {
4065       System.err.println("Assuming project file with "
4066               + (version == null ? "null" : version)
4067               + " is compatible with Jalview version " + supported);
4068       return true;
4069     }
4070     else
4071     {
4072       StringTokenizer currentV = new StringTokenizer(supported, "."), fileV = new StringTokenizer(
4073               version, ".");
4074       while (currentV.hasMoreTokens() && fileV.hasMoreTokens())
4075       {
4076         // convert b to decimal to catch bugfix releases within a series
4077         String curT = currentV.nextToken().toLowerCase().replace('b', '.');
4078         String fileT = fileV.nextToken().toLowerCase().replace('b', '.');
4079         try
4080         {
4081           float supportedVersionToken = Float.parseFloat(curT);
4082           float myVersiontoken = Float.parseFloat(fileT);
4083           if (supportedVersionToken > myVersiontoken)
4084           {
4085             // current version is newer than the version that wrote the file
4086             return false;
4087           }
4088           if (supportedVersionToken < myVersiontoken)
4089           {
4090             // current version is older than the version that wrote the file
4091             return true;
4092           }
4093         } catch (NumberFormatException nfe)
4094         {
4095           System.err
4096                   .println("** WARNING: Version comparison failed for tokens ("
4097                           + curT
4098                           + ") and ("
4099                           + fileT
4100                           + ")\n** Current: '"
4101                           + supported + "' and Version: '" + version + "'");
4102         }
4103       }
4104       if (currentV.hasMoreElements())
4105       {
4106         // fileV has no minor version but identical series to current
4107         return false;
4108       }
4109     }
4110     return true;
4111   }
4112
4113   Vector<JalviewStructureDisplayI> newStructureViewers = null;
4114
4115   protected void addNewStructureViewer(JalviewStructureDisplayI sview)
4116   {
4117     if (newStructureViewers != null)
4118     {
4119       sview.getBinding().setFinishedLoadingFromArchive(false);
4120       newStructureViewers.add(sview);
4121     }
4122   }
4123
4124   protected void setLoadingFinishedForNewStructureViewers()
4125   {
4126     if (newStructureViewers != null)
4127     {
4128       for (JalviewStructureDisplayI sview : newStructureViewers)
4129       {
4130         sview.getBinding().setFinishedLoadingFromArchive(true);
4131       }
4132       newStructureViewers.clear();
4133       newStructureViewers = null;
4134     }
4135   }
4136
4137   AlignFrame loadViewport(String file, JSeq[] JSEQ,
4138           List<SequenceI> hiddenSeqs, Alignment al,
4139           JalviewModelSequence jms, Viewport view, String uniqueSeqSetId,
4140           String viewId, List<JvAnnotRow> autoAlan)
4141   {
4142     AlignFrame af = null;
4143     af = new AlignFrame(al, view.getWidth(), view.getHeight(),
4144             uniqueSeqSetId, viewId);
4145
4146     af.setFileName(file, "Jalview");
4147
4148     for (int i = 0; i < JSEQ.length; i++)
4149     {
4150       af.viewport.setSequenceColour(af.viewport.getAlignment()
4151               .getSequenceAt(i), new java.awt.Color(JSEQ[i].getColour()));
4152     }
4153
4154     af.viewport.setGatherViewsHere(view.getGatheredViews());
4155
4156     if (view.getSequenceSetId() != null)
4157     {
4158       AlignmentViewport av = viewportsAdded.get(uniqueSeqSetId);
4159
4160       af.viewport.setSequenceSetId(uniqueSeqSetId);
4161       if (av != null)
4162       {
4163         // propagate shared settings to this new view
4164         af.viewport.setHistoryList(av.getHistoryList());
4165         af.viewport.setRedoList(av.getRedoList());
4166       }
4167       else
4168       {
4169         viewportsAdded.put(uniqueSeqSetId, af.viewport);
4170       }
4171       // TODO: check if this method can be called repeatedly without
4172       // side-effects if alignpanel already registered.
4173       PaintRefresher.Register(af.alignPanel, uniqueSeqSetId);
4174     }
4175     // apply Hidden regions to view.
4176     if (hiddenSeqs != null)
4177     {
4178       for (int s = 0; s < JSEQ.length; s++)
4179       {
4180         jalview.datamodel.SequenceGroup hidden = new jalview.datamodel.SequenceGroup();
4181
4182         for (int r = 0; r < JSEQ[s].getHiddenSequencesCount(); r++)
4183         {
4184           hidden.addSequence(
4185                   al.getSequenceAt(JSEQ[s].getHiddenSequences(r)), false);
4186         }
4187         af.viewport.hideRepSequences(al.getSequenceAt(s), hidden);
4188       }
4189
4190       // jalview.datamodel.SequenceI[] hseqs = new
4191       // jalview.datamodel.SequenceI[hiddenSeqs
4192       // .size()];
4193       //
4194       // for (int s = 0; s < hiddenSeqs.size(); s++)
4195       // {
4196       // hseqs[s] = (jalview.datamodel.SequenceI) hiddenSeqs.elementAt(s);
4197       // }
4198
4199       SequenceI[] hseqs = hiddenSeqs.toArray(new SequenceI[hiddenSeqs
4200               .size()]);
4201       af.viewport.hideSequence(hseqs);
4202
4203     }
4204     // recover view properties and display parameters
4205     if (view.getViewName() != null)
4206     {
4207       af.viewport.viewName = view.getViewName();
4208       af.setInitialTabVisible();
4209     }
4210     af.setBounds(view.getXpos(), view.getYpos(), view.getWidth(),
4211             view.getHeight());
4212
4213     af.viewport.setShowAnnotation(view.getShowAnnotation());
4214     af.viewport.setAbovePIDThreshold(view.getPidSelected());
4215
4216     af.viewport.setColourText(view.getShowColourText());
4217
4218     af.viewport.setConservationSelected(view.getConservationSelected());
4219     af.viewport.setShowJVSuffix(view.getShowFullId());
4220     af.viewport.setRightAlignIds(view.getRightAlignIds());
4221     af.viewport.setFont(
4222             new java.awt.Font(view.getFontName(), view.getFontStyle(), view
4223                     .getFontSize()), true);
4224     ViewStyleI vs = af.viewport.getViewStyle();
4225     vs.setScaleProteinAsCdna(view.isScaleProteinAsCdna());
4226     af.viewport.setViewStyle(vs);
4227     // TODO: allow custom charWidth/Heights to be restored by updating them
4228     // after setting font - which means set above to false
4229     af.viewport.setRenderGaps(view.getRenderGaps());
4230     af.viewport.setWrapAlignment(view.getWrapAlignment());
4231     af.viewport.setShowAnnotation(view.getShowAnnotation());
4232
4233     af.viewport.setShowBoxes(view.getShowBoxes());
4234
4235     af.viewport.setShowText(view.getShowText());
4236
4237     af.viewport.setTextColour(new java.awt.Color(view.getTextCol1()));
4238     af.viewport.setTextColour2(new java.awt.Color(view.getTextCol2()));
4239     af.viewport.setThresholdTextColour(view.getTextColThreshold());
4240     af.viewport.setShowUnconserved(view.hasShowUnconserved() ? view
4241             .isShowUnconserved() : false);
4242     af.viewport.setStartRes(view.getStartRes());
4243     af.viewport.setStartSeq(view.getStartSeq());
4244     af.alignPanel.updateLayout();
4245     ColourSchemeI cs = null;
4246     // apply colourschemes
4247     if (view.getBgColour() != null)
4248     {
4249       if (view.getBgColour().startsWith("ucs"))
4250       {
4251         cs = getUserColourScheme(jms, view.getBgColour());
4252       }
4253       else if (view.getBgColour().startsWith("Annotation"))
4254       {
4255         AnnotationColours viewAnnColour = view.getAnnotationColours();
4256         cs = constructAnnotationColour(viewAnnColour, af, al, jms, true);
4257
4258         // annpos
4259
4260       }
4261       else
4262       {
4263         cs = ColourSchemeProperty.getColour(al, view.getBgColour());
4264       }
4265
4266       if (cs != null)
4267       {
4268         cs.setThreshold(view.getPidThreshold(), true);
4269         cs.setConsensus(af.viewport.getSequenceConsensusHash());
4270       }
4271     }
4272
4273     af.viewport.setGlobalColourScheme(cs);
4274     af.viewport.setColourAppliesToAllGroups(false);
4275
4276     if (view.getConservationSelected() && cs != null)
4277     {
4278       cs.setConservationInc(view.getConsThreshold());
4279     }
4280
4281     af.changeColour(cs);
4282
4283     af.viewport.setColourAppliesToAllGroups(true);
4284
4285     af.viewport.setShowSequenceFeatures(view.getShowSequenceFeatures());
4286
4287     if (view.hasCentreColumnLabels())
4288     {
4289       af.viewport.setCentreColumnLabels(view.getCentreColumnLabels());
4290     }
4291     if (view.hasIgnoreGapsinConsensus())
4292     {
4293       af.viewport.setIgnoreGapsConsensus(view.getIgnoreGapsinConsensus(),
4294               null);
4295     }
4296     if (view.hasFollowHighlight())
4297     {
4298       af.viewport.setFollowHighlight(view.getFollowHighlight());
4299     }
4300     if (view.hasFollowSelection())
4301     {
4302       af.viewport.followSelection = view.getFollowSelection();
4303     }
4304     if (view.hasShowConsensusHistogram())
4305     {
4306       af.viewport.setShowConsensusHistogram(view
4307               .getShowConsensusHistogram());
4308     }
4309     else
4310     {
4311       af.viewport.setShowConsensusHistogram(true);
4312     }
4313     if (view.hasShowSequenceLogo())
4314     {
4315       af.viewport.setShowSequenceLogo(view.getShowSequenceLogo());
4316     }
4317     else
4318     {
4319       af.viewport.setShowSequenceLogo(false);
4320     }
4321     if (view.hasNormaliseSequenceLogo())
4322     {
4323       af.viewport.setNormaliseSequenceLogo(view.getNormaliseSequenceLogo());
4324     }
4325     if (view.hasShowDbRefTooltip())
4326     {
4327       af.viewport.setShowDBRefs(view.getShowDbRefTooltip());
4328     }
4329     if (view.hasShowNPfeatureTooltip())
4330     {
4331       af.viewport.setShowNPFeats(view.hasShowNPfeatureTooltip());
4332     }
4333     if (view.hasShowGroupConsensus())
4334     {
4335       af.viewport.setShowGroupConsensus(view.getShowGroupConsensus());
4336     }
4337     else
4338     {
4339       af.viewport.setShowGroupConsensus(false);
4340     }
4341     if (view.hasShowGroupConservation())
4342     {
4343       af.viewport.setShowGroupConservation(view.getShowGroupConservation());
4344     }
4345     else
4346     {
4347       af.viewport.setShowGroupConservation(false);
4348     }
4349
4350     // recover featre settings
4351     if (jms.getFeatureSettings() != null)
4352     {
4353       FeaturesDisplayed fdi;
4354       af.viewport.setFeaturesDisplayed(fdi = new FeaturesDisplayed());
4355       String[] renderOrder = new String[jms.getFeatureSettings()
4356               .getSettingCount()];
4357       Hashtable featureGroups = new Hashtable();
4358       Hashtable featureColours = new Hashtable();
4359       Hashtable featureOrder = new Hashtable();
4360
4361       for (int fs = 0; fs < jms.getFeatureSettings().getSettingCount(); fs++)
4362       {
4363         Setting setting = jms.getFeatureSettings().getSetting(fs);
4364         if (setting.hasMincolour())
4365         {
4366           GraduatedColor gc = setting.hasMin() ? new GraduatedColor(
4367                   new java.awt.Color(setting.getMincolour()),
4368                   new java.awt.Color(setting.getColour()),
4369                   setting.getMin(), setting.getMax()) : new GraduatedColor(
4370                   new java.awt.Color(setting.getMincolour()),
4371                   new java.awt.Color(setting.getColour()), 0, 1);
4372           if (setting.hasThreshold())
4373           {
4374             gc.setThresh(setting.getThreshold());
4375             gc.setThreshType(setting.getThreshstate());
4376           }
4377           gc.setAutoScaled(true); // default
4378           if (setting.hasAutoScale())
4379           {
4380             gc.setAutoScaled(setting.getAutoScale());
4381           }
4382           if (setting.hasColourByLabel())
4383           {
4384             gc.setColourByLabel(setting.getColourByLabel());
4385           }
4386           // and put in the feature colour table.
4387           featureColours.put(setting.getType(), gc);
4388         }
4389         else
4390         {
4391           featureColours.put(setting.getType(),
4392                   new java.awt.Color(setting.getColour()));
4393         }
4394         renderOrder[fs] = setting.getType();
4395         if (setting.hasOrder())
4396         {
4397           featureOrder.put(setting.getType(), setting.getOrder());
4398         }
4399         else
4400         {
4401           featureOrder.put(setting.getType(), new Float(fs
4402                   / jms.getFeatureSettings().getSettingCount()));
4403         }
4404         if (setting.getDisplay())
4405         {
4406           fdi.setVisible(setting.getType());
4407         }
4408       }
4409       Hashtable fgtable = new Hashtable();
4410       for (int gs = 0; gs < jms.getFeatureSettings().getGroupCount(); gs++)
4411       {
4412         Group grp = jms.getFeatureSettings().getGroup(gs);
4413         fgtable.put(grp.getName(), new Boolean(grp.getDisplay()));
4414       }
4415       // FeatureRendererSettings frs = new FeatureRendererSettings(renderOrder,
4416       // fgtable, featureColours, jms.getFeatureSettings().hasTransparency() ?
4417       // jms.getFeatureSettings().getTransparency() : 0.0, featureOrder);
4418       FeatureRendererSettings frs = new FeatureRendererSettings(
4419               renderOrder, fgtable, featureColours, 1.0f, featureOrder);
4420       af.alignPanel.getSeqPanel().seqCanvas.getFeatureRenderer()
4421               .transferSettings(frs);
4422
4423     }
4424
4425     if (view.getHiddenColumnsCount() > 0)
4426     {
4427       for (int c = 0; c < view.getHiddenColumnsCount(); c++)
4428       {
4429         af.viewport.hideColumns(view.getHiddenColumns(c).getStart(), view
4430                 .getHiddenColumns(c).getEnd() // +1
4431                 );
4432       }
4433     }
4434     if (view.getCalcIdParam() != null)
4435     {
4436       for (CalcIdParam calcIdParam : view.getCalcIdParam())
4437       {
4438         if (calcIdParam != null)
4439         {
4440           if (recoverCalcIdParam(calcIdParam, af.viewport))
4441           {
4442           }
4443           else
4444           {
4445             warn("Couldn't recover parameters for "
4446                     + calcIdParam.getCalcId());
4447           }
4448         }
4449       }
4450     }
4451     af.setMenusFromViewport(af.viewport);
4452
4453     // TODO: we don't need to do this if the viewport is aready visible.
4454     /*
4455      * Add the AlignFrame to the desktop (it may be 'gathered' later), unless it
4456      * has a 'cdna/protein complement' view, in which case save it in order to
4457      * populate a SplitFrame once all views have been read in.
4458      */
4459     String complementaryViewId = view.getComplementId();
4460     if (complementaryViewId == null)
4461     {
4462       Desktop.addInternalFrame(af, view.getTitle(), view.getWidth(),
4463               view.getHeight());
4464       // recompute any autoannotation
4465       af.alignPanel.updateAnnotation(false, true);
4466       reorderAutoannotation(af, al, autoAlan);
4467       af.alignPanel.alignmentChanged();
4468     }
4469     else
4470     {
4471       splitFrameCandidates.put(view, af);
4472     }
4473     return af;
4474   }
4475
4476   private ColourSchemeI constructAnnotationColour(
4477           AnnotationColours viewAnnColour, AlignFrame af, Alignment al,
4478           JalviewModelSequence jms, boolean checkGroupAnnColour)
4479   {
4480     boolean propagateAnnColour = false;
4481     ColourSchemeI cs = null;
4482     AlignmentI annAlignment = af != null ? af.viewport.getAlignment() : al;
4483     if (checkGroupAnnColour && al.getGroups() != null
4484             && al.getGroups().size() > 0)
4485     {
4486       // pre 2.8.1 behaviour
4487       // check to see if we should transfer annotation colours
4488       propagateAnnColour = true;
4489       for (jalview.datamodel.SequenceGroup sg : al.getGroups())
4490       {
4491         if (sg.cs instanceof AnnotationColourGradient)
4492         {
4493           propagateAnnColour = false;
4494         }
4495       }
4496     }
4497     // int find annotation
4498     if (annAlignment.getAlignmentAnnotation() != null)
4499     {
4500       for (int i = 0; i < annAlignment.getAlignmentAnnotation().length; i++)
4501       {
4502         if (annAlignment.getAlignmentAnnotation()[i].label
4503                 .equals(viewAnnColour.getAnnotation()))
4504         {
4505           if (annAlignment.getAlignmentAnnotation()[i].getThreshold() == null)
4506           {
4507             annAlignment.getAlignmentAnnotation()[i]
4508                     .setThreshold(new jalview.datamodel.GraphLine(
4509                             viewAnnColour.getThreshold(), "Threshold",
4510                             java.awt.Color.black)
4511
4512                     );
4513           }
4514
4515           if (viewAnnColour.getColourScheme().equals("None"))
4516           {
4517             cs = new AnnotationColourGradient(
4518                     annAlignment.getAlignmentAnnotation()[i],
4519                     new java.awt.Color(viewAnnColour.getMinColour()),
4520                     new java.awt.Color(viewAnnColour.getMaxColour()),
4521                     viewAnnColour.getAboveThreshold());
4522           }
4523           else if (viewAnnColour.getColourScheme().startsWith("ucs"))
4524           {
4525             cs = new AnnotationColourGradient(
4526                     annAlignment.getAlignmentAnnotation()[i],
4527                     getUserColourScheme(jms,
4528                             viewAnnColour.getColourScheme()),
4529                     viewAnnColour.getAboveThreshold());
4530           }
4531           else
4532           {
4533             cs = new AnnotationColourGradient(
4534                     annAlignment.getAlignmentAnnotation()[i],
4535                     ColourSchemeProperty.getColour(al,
4536                             viewAnnColour.getColourScheme()),
4537                     viewAnnColour.getAboveThreshold());
4538           }
4539           if (viewAnnColour.hasPerSequence())
4540           {
4541             ((AnnotationColourGradient) cs).setSeqAssociated(viewAnnColour
4542                     .isPerSequence());
4543           }
4544           if (viewAnnColour.hasPredefinedColours())
4545           {
4546             ((AnnotationColourGradient) cs)
4547                     .setPredefinedColours(viewAnnColour
4548                             .isPredefinedColours());
4549           }
4550           if (propagateAnnColour && al.getGroups() != null)
4551           {
4552             // Also use these settings for all the groups
4553             for (int g = 0; g < al.getGroups().size(); g++)
4554             {
4555               jalview.datamodel.SequenceGroup sg = al.getGroups().get(g);
4556
4557               if (sg.cs == null)
4558               {
4559                 continue;
4560               }
4561
4562               /*
4563                * if (viewAnnColour.getColourScheme().equals("None" )) { sg.cs =
4564                * new AnnotationColourGradient(
4565                * annAlignment.getAlignmentAnnotation()[i], new
4566                * java.awt.Color(viewAnnColour. getMinColour()), new
4567                * java.awt.Color(viewAnnColour. getMaxColour()),
4568                * viewAnnColour.getAboveThreshold()); } else
4569                */
4570               {
4571                 sg.cs = new AnnotationColourGradient(
4572                         annAlignment.getAlignmentAnnotation()[i], sg.cs,
4573                         viewAnnColour.getAboveThreshold());
4574                 if (cs instanceof AnnotationColourGradient)
4575                 {
4576                   if (viewAnnColour.hasPerSequence())
4577                   {
4578                     ((AnnotationColourGradient) cs)
4579                             .setSeqAssociated(viewAnnColour.isPerSequence());
4580                   }
4581                   if (viewAnnColour.hasPredefinedColours())
4582                   {
4583                     ((AnnotationColourGradient) cs)
4584                             .setPredefinedColours(viewAnnColour
4585                                     .isPredefinedColours());
4586                   }
4587                 }
4588               }
4589
4590             }
4591           }
4592
4593           break;
4594         }
4595
4596       }
4597     }
4598     return cs;
4599   }
4600
4601   private void reorderAutoannotation(AlignFrame af, Alignment al,
4602           List<JvAnnotRow> autoAlan)
4603   {
4604     // copy over visualization settings for autocalculated annotation in the
4605     // view
4606     if (al.getAlignmentAnnotation() != null)
4607     {
4608       /**
4609        * Kludge for magic autoannotation names (see JAL-811)
4610        */
4611       String[] magicNames = new String[] { "Consensus", "Quality",
4612           "Conservation" };
4613       JvAnnotRow nullAnnot = new JvAnnotRow(-1, null);
4614       Hashtable<String, JvAnnotRow> visan = new Hashtable<String, JvAnnotRow>();
4615       for (String nm : magicNames)
4616       {
4617         visan.put(nm, nullAnnot);
4618       }
4619       for (JvAnnotRow auan : autoAlan)
4620       {
4621         visan.put(auan.template.label
4622                 + (auan.template.getCalcId() == null ? "" : "\t"
4623                         + auan.template.getCalcId()), auan);
4624       }
4625       int hSize = al.getAlignmentAnnotation().length;
4626       List<JvAnnotRow> reorder = new ArrayList<JvAnnotRow>();
4627       // work through any autoCalculated annotation already on the view
4628       // removing it if it should be placed in a different location on the
4629       // annotation panel.
4630       List<String> remains = new ArrayList<String>(visan.keySet());
4631       for (int h = 0; h < hSize; h++)
4632       {
4633         jalview.datamodel.AlignmentAnnotation jalan = al
4634                 .getAlignmentAnnotation()[h];
4635         if (jalan.autoCalculated)
4636         {
4637           String k;
4638           JvAnnotRow valan = visan.get(k = jalan.label);
4639           if (jalan.getCalcId() != null)
4640           {
4641             valan = visan.get(k = jalan.label + "\t" + jalan.getCalcId());
4642           }
4643
4644           if (valan != null)
4645           {
4646             // delete the auto calculated row from the alignment
4647             al.deleteAnnotation(jalan, false);
4648             remains.remove(k);
4649             hSize--;
4650             h--;
4651             if (valan != nullAnnot)
4652             {
4653               if (jalan != valan.template)
4654               {
4655                 // newly created autoannotation row instance
4656                 // so keep a reference to the visible annotation row
4657                 // and copy over all relevant attributes
4658                 if (valan.template.graphHeight >= 0)
4659
4660                 {
4661                   jalan.graphHeight = valan.template.graphHeight;
4662                 }
4663                 jalan.visible = valan.template.visible;
4664               }
4665               reorder.add(new JvAnnotRow(valan.order, jalan));
4666             }
4667           }
4668         }
4669       }
4670       // Add any (possibly stale) autocalculated rows that were not appended to
4671       // the view during construction
4672       for (String other : remains)
4673       {
4674         JvAnnotRow othera = visan.get(other);
4675         if (othera != nullAnnot && othera.template.getCalcId() != null
4676                 && othera.template.getCalcId().length() > 0)
4677         {
4678           reorder.add(othera);
4679         }
4680       }
4681       // now put the automatic annotation in its correct place
4682       int s = 0, srt[] = new int[reorder.size()];
4683       JvAnnotRow[] rws = new JvAnnotRow[reorder.size()];
4684       for (JvAnnotRow jvar : reorder)
4685       {
4686         rws[s] = jvar;
4687         srt[s++] = jvar.order;
4688       }
4689       reorder.clear();
4690       jalview.util.QuickSort.sort(srt, rws);
4691       // and re-insert the annotation at its correct position
4692       for (JvAnnotRow jvar : rws)
4693       {
4694         al.addAnnotation(jvar.template, jvar.order);
4695       }
4696       af.alignPanel.adjustAnnotationHeight();
4697     }
4698   }
4699
4700   Hashtable skipList = null;
4701
4702   /**
4703    * TODO remove this method
4704    * 
4705    * @param view
4706    * @return AlignFrame bound to sequenceSetId from view, if one exists. private
4707    *         AlignFrame getSkippedFrame(Viewport view) { if (skipList==null) {
4708    *         throw new Error("Implementation Error. No skipList defined for this
4709    *         Jalview2XML instance."); } return (AlignFrame)
4710    *         skipList.get(view.getSequenceSetId()); }
4711    */
4712
4713   /**
4714    * Check if the Jalview view contained in object should be skipped or not.
4715    * 
4716    * @param object
4717    * @return true if view's sequenceSetId is a key in skipList
4718    */
4719   private boolean skipViewport(JalviewModel object)
4720   {
4721     if (skipList == null)
4722     {
4723       return false;
4724     }
4725     String id;
4726     if (skipList.containsKey(id = object.getJalviewModelSequence()
4727             .getViewport()[0].getSequenceSetId()))
4728     {
4729       if (Cache.log != null && Cache.log.isDebugEnabled())
4730       {
4731         Cache.log.debug("Skipping seuqence set id " + id);
4732       }
4733       return true;
4734     }
4735     return false;
4736   }
4737
4738   public void addToSkipList(AlignFrame af)
4739   {
4740     if (skipList == null)
4741     {
4742       skipList = new Hashtable();
4743     }
4744     skipList.put(af.getViewport().getSequenceSetId(), af);
4745   }
4746
4747   public void clearSkipList()
4748   {
4749     if (skipList != null)
4750     {
4751       skipList.clear();
4752       skipList = null;
4753     }
4754   }
4755
4756   private void recoverDatasetFor(SequenceSet vamsasSet, Alignment al,
4757           boolean ignoreUnrefed)
4758   {
4759     jalview.datamodel.Alignment ds = getDatasetFor(vamsasSet.getDatasetId());
4760     Vector dseqs = null;
4761     if (ds == null)
4762     {
4763       // create a list of new dataset sequences
4764       dseqs = new Vector();
4765     }
4766     for (int i = 0, iSize = vamsasSet.getSequenceCount(); i < iSize; i++)
4767     {
4768       Sequence vamsasSeq = vamsasSet.getSequence(i);
4769       ensureJalviewDatasetSequence(vamsasSeq, ds, dseqs, ignoreUnrefed);
4770     }
4771     // create a new dataset
4772     if (ds == null)
4773     {
4774       SequenceI[] dsseqs = new SequenceI[dseqs.size()];
4775       dseqs.copyInto(dsseqs);
4776       ds = new jalview.datamodel.Alignment(dsseqs);
4777       debug("Created new dataset " + vamsasSet.getDatasetId()
4778               + " for alignment " + System.identityHashCode(al));
4779       addDatasetRef(vamsasSet.getDatasetId(), ds);
4780     }
4781     // set the dataset for the newly imported alignment.
4782     if (al.getDataset() == null && !ignoreUnrefed)
4783     {
4784       al.setDataset(ds);
4785     }
4786   }
4787
4788   /**
4789    * 
4790    * @param vamsasSeq
4791    *          sequence definition to create/merge dataset sequence for
4792    * @param ds
4793    *          dataset alignment
4794    * @param dseqs
4795    *          vector to add new dataset sequence to
4796    */
4797   private void ensureJalviewDatasetSequence(Sequence vamsasSeq,
4798           AlignmentI ds, Vector dseqs, boolean ignoreUnrefed)
4799   {
4800     // JBP TODO: Check this is called for AlCodonFrames to support recovery of
4801     // xRef Codon Maps
4802     SequenceI sq = seqRefIds.get(vamsasSeq.getId());
4803     SequenceI dsq = null;
4804     if (sq != null && sq.getDatasetSequence() != null)
4805     {
4806       dsq = sq.getDatasetSequence();
4807     }
4808     if (sq == null && ignoreUnrefed)
4809     {
4810       return;
4811     }
4812     String sqid = vamsasSeq.getDsseqid();
4813     if (dsq == null)
4814     {
4815       // need to create or add a new dataset sequence reference to this sequence
4816       if (sqid != null)
4817       {
4818         dsq = seqRefIds.get(sqid);
4819       }
4820       // check again
4821       if (dsq == null)
4822       {
4823         // make a new dataset sequence
4824         dsq = sq.createDatasetSequence();
4825         if (sqid == null)
4826         {
4827           // make up a new dataset reference for this sequence
4828           sqid = seqHash(dsq);
4829         }
4830         dsq.setVamsasId(uniqueSetSuffix + sqid);
4831         seqRefIds.put(sqid, dsq);
4832         if (ds == null)
4833         {
4834           if (dseqs != null)
4835           {
4836             dseqs.addElement(dsq);
4837           }
4838         }
4839         else
4840         {
4841           ds.addSequence(dsq);
4842         }
4843       }
4844       else
4845       {
4846         if (sq != dsq)
4847         { // make this dataset sequence sq's dataset sequence
4848           sq.setDatasetSequence(dsq);
4849           // and update the current dataset alignment
4850           if (ds == null)
4851           {
4852             if (dseqs != null)
4853             {
4854               if (!dseqs.contains(dsq))
4855               {
4856                 dseqs.add(dsq);
4857               }
4858             }
4859             else
4860             {
4861               if (ds.findIndex(dsq) < 0)
4862               {
4863                 ds.addSequence(dsq);
4864               }
4865             }
4866           }
4867         }
4868       }
4869     }
4870     // TODO: refactor this as a merge dataset sequence function
4871     // now check that sq (the dataset sequence) sequence really is the union of
4872     // all references to it
4873     // boolean pre = sq.getStart() < dsq.getStart();
4874     // boolean post = sq.getEnd() > dsq.getEnd();
4875     // if (pre || post)
4876     if (sq != dsq)
4877     {
4878       // StringBuffer sb = new StringBuffer();
4879       String newres = jalview.analysis.AlignSeq.extractGaps(
4880               jalview.util.Comparison.GapChars, sq.getSequenceAsString());
4881       if (!newres.equalsIgnoreCase(dsq.getSequenceAsString())
4882               && newres.length() > dsq.getLength())
4883       {
4884         // Update with the longer sequence.
4885         synchronized (dsq)
4886         {
4887           /*
4888            * if (pre) { sb.insert(0, newres .substring(0, dsq.getStart() -
4889            * sq.getStart())); dsq.setStart(sq.getStart()); } if (post) {
4890            * sb.append(newres.substring(newres.length() - sq.getEnd() -
4891            * dsq.getEnd())); dsq.setEnd(sq.getEnd()); }
4892            */
4893           dsq.setSequence(newres);
4894         }
4895         // TODO: merges will never happen if we 'know' we have the real dataset
4896         // sequence - this should be detected when id==dssid
4897         System.err
4898                 .println("DEBUG Notice:  Merged dataset sequence (if you see this often, post at http://issues.jalview.org/browse/JAL-1474)"); // ("
4899         // + (pre ? "prepended" : "") + " "
4900         // + (post ? "appended" : ""));
4901       }
4902     }
4903   }
4904
4905   /*
4906    * TODO use AlignmentI here and in related methods - needs
4907    * AlignmentI.getDataset() changed to return AlignmentI instead of Alignment
4908    */
4909   Hashtable<String, Alignment> datasetIds = null;
4910
4911   IdentityHashMap<Alignment, String> dataset2Ids = null;
4912
4913   private Alignment getDatasetFor(String datasetId)
4914   {
4915     if (datasetIds == null)
4916     {
4917       datasetIds = new Hashtable<String, Alignment>();
4918       return null;
4919     }
4920     if (datasetIds.containsKey(datasetId))
4921     {
4922       return datasetIds.get(datasetId);
4923     }
4924     return null;
4925   }
4926
4927   private void addDatasetRef(String datasetId, Alignment dataset)
4928   {
4929     if (datasetIds == null)
4930     {
4931       datasetIds = new Hashtable<String, Alignment>();
4932     }
4933     datasetIds.put(datasetId, dataset);
4934   }
4935
4936   /**
4937    * make a new dataset ID for this jalview dataset alignment
4938    * 
4939    * @param dataset
4940    * @return
4941    */
4942   private String getDatasetIdRef(Alignment dataset)
4943   {
4944     if (dataset.getDataset() != null)
4945     {
4946       warn("Serious issue!  Dataset Object passed to getDatasetIdRef is not a Jalview DATASET alignment...");
4947     }
4948     String datasetId = makeHashCode(dataset, null);
4949     if (datasetId == null)
4950     {
4951       // make a new datasetId and record it
4952       if (dataset2Ids == null)
4953       {
4954         dataset2Ids = new IdentityHashMap<Alignment, String>();
4955       }
4956       else
4957       {
4958         datasetId = dataset2Ids.get(dataset);
4959       }
4960       if (datasetId == null)
4961       {
4962         datasetId = "ds" + dataset2Ids.size() + 1;
4963         dataset2Ids.put(dataset, datasetId);
4964       }
4965     }
4966     return datasetId;
4967   }
4968
4969   private void addDBRefs(SequenceI datasetSequence, Sequence sequence)
4970   {
4971     for (int d = 0; d < sequence.getDBRefCount(); d++)
4972     {
4973       DBRef dr = sequence.getDBRef(d);
4974       jalview.datamodel.DBRefEntry entry = new jalview.datamodel.DBRefEntry(
4975               sequence.getDBRef(d).getSource(), sequence.getDBRef(d)
4976                       .getVersion(), sequence.getDBRef(d).getAccessionId());
4977       if (dr.getMapping() != null)
4978       {
4979         entry.setMap(addMapping(dr.getMapping()));
4980       }
4981       datasetSequence.addDBRef(entry);
4982     }
4983   }
4984
4985   private jalview.datamodel.Mapping addMapping(Mapping m)
4986   {
4987     SequenceI dsto = null;
4988     // Mapping m = dr.getMapping();
4989     int fr[] = new int[m.getMapListFromCount() * 2];
4990     Enumeration f = m.enumerateMapListFrom();
4991     for (int _i = 0; f.hasMoreElements(); _i += 2)
4992     {
4993       MapListFrom mf = (MapListFrom) f.nextElement();
4994       fr[_i] = mf.getStart();
4995       fr[_i + 1] = mf.getEnd();
4996     }
4997     int fto[] = new int[m.getMapListToCount() * 2];
4998     f = m.enumerateMapListTo();
4999     for (int _i = 0; f.hasMoreElements(); _i += 2)
5000     {
5001       MapListTo mf = (MapListTo) f.nextElement();
5002       fto[_i] = mf.getStart();
5003       fto[_i + 1] = mf.getEnd();
5004     }
5005     jalview.datamodel.Mapping jmap = new jalview.datamodel.Mapping(dsto,
5006             fr, fto, (int) m.getMapFromUnit(), (int) m.getMapToUnit());
5007     if (m.getMappingChoice() != null)
5008     {
5009       MappingChoice mc = m.getMappingChoice();
5010       if (mc.getDseqFor() != null)
5011       {
5012         String dsfor = "" + mc.getDseqFor();
5013         if (seqRefIds.containsKey(dsfor))
5014         {
5015           /**
5016            * recover from hash
5017            */
5018           jmap.setTo(seqRefIds.get(dsfor));
5019         }
5020         else
5021         {
5022           frefedSequence.add(new Object[] { dsfor, jmap });
5023         }
5024       }
5025       else
5026       {
5027         /**
5028          * local sequence definition
5029          */
5030         Sequence ms = mc.getSequence();
5031         SequenceI djs = null;
5032         String sqid = ms.getDsseqid();
5033         if (sqid != null && sqid.length() > 0)
5034         {
5035           /*
5036            * recover dataset sequence
5037            */
5038           djs = seqRefIds.get(sqid);
5039         }
5040         else
5041         {
5042           System.err
5043                   .println("Warning - making up dataset sequence id for DbRef sequence map reference");
5044           sqid = ((Object) ms).toString(); // make up a new hascode for
5045           // undefined dataset sequence hash
5046           // (unlikely to happen)
5047         }
5048
5049         if (djs == null)
5050         {
5051           /**
5052            * make a new dataset sequence and add it to refIds hash
5053            */
5054           djs = new jalview.datamodel.Sequence(ms.getName(),
5055                   ms.getSequence());
5056           djs.setStart(jmap.getMap().getToLowest());
5057           djs.setEnd(jmap.getMap().getToHighest());
5058           djs.setVamsasId(uniqueSetSuffix + sqid);
5059           jmap.setTo(djs);
5060           seqRefIds.put(sqid, djs);
5061
5062         }
5063         jalview.bin.Cache.log.debug("about to recurse on addDBRefs.");
5064         addDBRefs(djs, ms);
5065
5066       }
5067     }
5068     return (jmap);
5069
5070   }
5071
5072   public jalview.gui.AlignmentPanel copyAlignPanel(AlignmentPanel ap,
5073           boolean keepSeqRefs)
5074   {
5075     initSeqRefs();
5076     JalviewModel jm = saveState(ap, null, null, null);
5077
5078     if (!keepSeqRefs)
5079     {
5080       clearSeqRefs();
5081       jm.getJalviewModelSequence().getViewport(0).setSequenceSetId(null);
5082     }
5083     else
5084     {
5085       uniqueSetSuffix = "";
5086       jm.getJalviewModelSequence().getViewport(0).setId(null); // we don't
5087       // overwrite the
5088       // view we just
5089       // copied
5090     }
5091     if (this.frefedSequence == null)
5092     {
5093       frefedSequence = new Vector();
5094     }
5095
5096     viewportsAdded.clear();
5097
5098     AlignFrame af = loadFromObject(jm, null, false, null);
5099     af.alignPanels.clear();
5100     af.closeMenuItem_actionPerformed(true);
5101
5102     /*
5103      * if(ap.av.getAlignment().getAlignmentAnnotation()!=null) { for(int i=0;
5104      * i<ap.av.getAlignment().getAlignmentAnnotation().length; i++) {
5105      * if(!ap.av.getAlignment().getAlignmentAnnotation()[i].autoCalculated) {
5106      * af.alignPanel.av.getAlignment().getAlignmentAnnotation()[i] =
5107      * ap.av.getAlignment().getAlignmentAnnotation()[i]; } } }
5108      */
5109
5110     return af.alignPanel;
5111   }
5112
5113   /**
5114    * flag indicating if hashtables should be cleared on finalization TODO this
5115    * flag may not be necessary
5116    */
5117   private final boolean _cleartables = true;
5118
5119   private Hashtable jvids2vobj;
5120
5121   /*
5122    * (non-Javadoc)
5123    * 
5124    * @see java.lang.Object#finalize()
5125    */
5126   @Override
5127   protected void finalize() throws Throwable
5128   {
5129     // really make sure we have no buried refs left.
5130     if (_cleartables)
5131     {
5132       clearSeqRefs();
5133     }
5134     this.seqRefIds = null;
5135     this.seqsToIds = null;
5136     super.finalize();
5137   }
5138
5139   private void warn(String msg)
5140   {
5141     warn(msg, null);
5142   }
5143
5144   private void warn(String msg, Exception e)
5145   {
5146     if (Cache.log != null)
5147     {
5148       if (e != null)
5149       {
5150         Cache.log.warn(msg, e);
5151       }
5152       else
5153       {
5154         Cache.log.warn(msg);
5155       }
5156     }
5157     else
5158     {
5159       System.err.println("Warning: " + msg);
5160       if (e != null)
5161       {
5162         e.printStackTrace();
5163       }
5164     }
5165   }
5166
5167   private void debug(String string)
5168   {
5169     debug(string, null);
5170   }
5171
5172   private void debug(String msg, Exception e)
5173   {
5174     if (Cache.log != null)
5175     {
5176       if (e != null)
5177       {
5178         Cache.log.debug(msg, e);
5179       }
5180       else
5181       {
5182         Cache.log.debug(msg);
5183       }
5184     }
5185     else
5186     {
5187       System.err.println("Warning: " + msg);
5188       if (e != null)
5189       {
5190         e.printStackTrace();
5191       }
5192     }
5193   }
5194
5195   /**
5196    * set the object to ID mapping tables used to write/recover objects and XML
5197    * ID strings for the jalview project. If external tables are provided then
5198    * finalize and clearSeqRefs will not clear the tables when the Jalview2XML
5199    * object goes out of scope. - also populates the datasetIds hashtable with
5200    * alignment objects containing dataset sequences
5201    * 
5202    * @param vobj2jv
5203    *          Map from ID strings to jalview datamodel
5204    * @param jv2vobj
5205    *          Map from jalview datamodel to ID strings
5206    * 
5207    * 
5208    */
5209   public void setObjectMappingTables(Hashtable vobj2jv,
5210           IdentityHashMap jv2vobj)
5211   {
5212     this.jv2vobj = jv2vobj;
5213     this.vobj2jv = vobj2jv;
5214     Iterator ds = jv2vobj.keySet().iterator();
5215     String id;
5216     while (ds.hasNext())
5217     {
5218       Object jvobj = ds.next();
5219       id = jv2vobj.get(jvobj).toString();
5220       if (jvobj instanceof jalview.datamodel.Alignment)
5221       {
5222         if (((jalview.datamodel.Alignment) jvobj).getDataset() == null)
5223         {
5224           addDatasetRef(id, (jalview.datamodel.Alignment) jvobj);
5225         }
5226       }
5227       else if (jvobj instanceof jalview.datamodel.Sequence)
5228       {
5229         // register sequence object so the XML parser can recover it.
5230         if (seqRefIds == null)
5231         {
5232           seqRefIds = new HashMap<String, SequenceI>();
5233         }
5234         if (seqsToIds == null)
5235         {
5236           seqsToIds = new IdentityHashMap<SequenceI, String>();
5237         }
5238         seqRefIds.put(jv2vobj.get(jvobj).toString(), (SequenceI) jvobj);
5239         seqsToIds.put((SequenceI) jvobj, id);
5240       }
5241       else if (jvobj instanceof jalview.datamodel.AlignmentAnnotation)
5242       {
5243         String anid;
5244         AlignmentAnnotation jvann = (AlignmentAnnotation) jvobj;
5245         annotationIds.put(anid = jv2vobj.get(jvobj).toString(), jvann);
5246         if (jvann.annotationId == null)
5247         {
5248           jvann.annotationId = anid;
5249         }
5250         if (!jvann.annotationId.equals(anid))
5251         {
5252           // TODO verify that this is the correct behaviour
5253           this.warn("Overriding Annotation ID for " + anid
5254                   + " from different id : " + jvann.annotationId);
5255           jvann.annotationId = anid;
5256         }
5257       }
5258       else if (jvobj instanceof String)
5259       {
5260         if (jvids2vobj == null)
5261         {
5262           jvids2vobj = new Hashtable();
5263           jvids2vobj.put(jvobj, jv2vobj.get(jvobj).toString());
5264         }
5265       }
5266       else
5267       {
5268         Cache.log.debug("Ignoring " + jvobj.getClass() + " (ID = " + id);
5269       }
5270     }
5271   }
5272
5273   /**
5274    * set the uniqueSetSuffix used to prefix/suffix object IDs for jalview
5275    * objects created from the project archive. If string is null (default for
5276    * construction) then suffix will be set automatically.
5277    * 
5278    * @param string
5279    */
5280   public void setUniqueSetSuffix(String string)
5281   {
5282     uniqueSetSuffix = string;
5283
5284   }
5285
5286   /**
5287    * uses skipList2 as the skipList for skipping views on sequence sets
5288    * associated with keys in the skipList
5289    * 
5290    * @param skipList2
5291    */
5292   public void setSkipList(Hashtable skipList2)
5293   {
5294     skipList = skipList2;
5295   }
5296
5297   /**
5298    * Reads the jar entry of given name and returns its contents, or null if the
5299    * entry is not found.
5300    * 
5301    * @param jprovider
5302    * @param jarEntryName
5303    * @return
5304    */
5305   protected String readJarEntry(jarInputStreamProvider jprovider,
5306           String jarEntryName)
5307   {
5308     String result = null;
5309     BufferedReader in = null;
5310
5311     try
5312     {
5313       /*
5314        * Reopen the jar input stream and traverse its entries to find a matching
5315        * name
5316        */
5317       JarInputStream jin = jprovider.getJarInputStream();
5318       JarEntry entry = null;
5319       do
5320       {
5321         entry = jin.getNextJarEntry();
5322       } while (entry != null && !entry.getName().equals(jarEntryName));
5323
5324       if (entry != null)
5325       {
5326         StringBuilder out = new StringBuilder(256);
5327         in = new BufferedReader(new InputStreamReader(jin, UTF_8));
5328         String data;
5329
5330         while ((data = in.readLine()) != null)
5331         {
5332           out.append(data);
5333         }
5334         result = out.toString();
5335       }
5336       else
5337       {
5338         warn("Couldn't find entry in Jalview Jar for " + jarEntryName);
5339       }
5340     } catch (Exception ex)
5341     {
5342       ex.printStackTrace();
5343     } finally
5344     {
5345       if (in != null)
5346       {
5347         try
5348         {
5349           in.close();
5350         } catch (IOException e)
5351         {
5352           // ignore
5353         }
5354       }
5355     }
5356
5357     return result;
5358   }
5359
5360   /**
5361    * Returns an incrementing counter (0, 1, 2...)
5362    * 
5363    * @return
5364    */
5365   private synchronized int nextCounter()
5366   {
5367     return counter++;
5368   }
5369 }