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