JAL-2023 fix for CDS shared dataset, robust load of split frame project
[jalview.git] / src / jalview / gui / Jalview2XML.java
1 /*
2  * Jalview - A Sequence Alignment Editor and Viewer (Version 2.9.0b2)
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<Object[]> frefedSequence = null;
167
168   boolean raiseGUI = true; // whether errors are raised in dialog boxes or not
169
170   /*
171    * Map of reconstructed AlignFrame objects that appear to have come from
172    * SplitFrame objects (have a dna/protein complement view).
173    */
174   private Map<Viewport, AlignFrame> splitFrameCandidates = new HashMap<Viewport, AlignFrame>();
175
176   /*
177    * Map from displayed rna structure models to their saved session state jar
178    * entry names
179    */
180   private Map<RnaModel, String> rnaSessions = new HashMap<RnaModel, String>();
181
182   /**
183    * create/return unique hash string for sq
184    * 
185    * @param sq
186    * @return new or existing unique string for sq
187    */
188   String seqHash(SequenceI sq)
189   {
190     if (seqsToIds == null)
191     {
192       initSeqRefs();
193     }
194     if (seqsToIds.containsKey(sq))
195     {
196       return seqsToIds.get(sq);
197     }
198     else
199     {
200       // create sequential key
201       String key = "sq" + (seqsToIds.size() + 1);
202       key = makeHashCode(sq, key); // check we don't have an external reference
203       // for it already.
204       seqsToIds.put(sq, key);
205       return key;
206     }
207   }
208
209   void clearSeqRefs()
210   {
211     if (_cleartables)
212     {
213       if (seqRefIds != null)
214       {
215         seqRefIds.clear();
216       }
217       if (seqsToIds != null)
218       {
219         seqsToIds.clear();
220       }
221       // seqRefIds = null;
222       // seqsToIds = null;
223     }
224     else
225     {
226       // do nothing
227       warn("clearSeqRefs called when _cleartables was not set. Doing nothing.");
228       // seqRefIds = new Hashtable();
229       // seqsToIds = new IdentityHashMap();
230     }
231   }
232
233   void initSeqRefs()
234   {
235     if (seqsToIds == null)
236     {
237       seqsToIds = new IdentityHashMap<SequenceI, String>();
238     }
239     if (seqRefIds == null)
240     {
241       seqRefIds = new HashMap<String, SequenceI>();
242     }
243   }
244
245   public Jalview2XML()
246   {
247   }
248
249   public Jalview2XML(boolean raiseGUI)
250   {
251     this.raiseGUI = raiseGUI;
252   }
253
254   public void resolveFrefedSequences()
255   {
256     if (frefedSequence.size() > 0)
257     {
258       int r = 0, rSize = frefedSequence.size();
259       while (r < rSize)
260       {
261         Object[] ref = frefedSequence.elementAt(r);
262         if (ref != null)
263         {
264           String sref = (String) ref[0];
265           if (seqRefIds.containsKey(sref))
266           {
267             if (ref[1] instanceof jalview.datamodel.Mapping)
268             {
269               SequenceI seq = seqRefIds.get(sref);
270               while (seq.getDatasetSequence() != null)
271               {
272                 seq = seq.getDatasetSequence();
273               }
274               ((jalview.datamodel.Mapping) ref[1]).setTo(seq);
275             }
276             else
277             {
278               if (ref[1] instanceof jalview.datamodel.AlignedCodonFrame)
279               {
280                 SequenceI seq = seqRefIds.get(sref);
281                 while (seq.getDatasetSequence() != null)
282                 {
283                   seq = seq.getDatasetSequence();
284                 }
285                 if (ref[2] != null
286                         && ref[2] instanceof jalview.datamodel.Mapping)
287                 {
288                   jalview.datamodel.Mapping mp = (jalview.datamodel.Mapping) ref[2];
289                   ((jalview.datamodel.AlignedCodonFrame) ref[1]).addMap(
290                           seq, mp.getTo(), mp.getMap());
291                 }
292                 else
293                 {
294                   System.err
295                           .println("IMPLEMENTATION ERROR: Unimplemented forward sequence references for AlcodonFrames involving "
296                                   + ref[2].getClass() + " type objects.");
297                 }
298               }
299               else
300               {
301                 System.err
302                         .println("IMPLEMENTATION ERROR: Unimplemented forward sequence references for "
303                                 + ref[1].getClass() + " type objects.");
304               }
305             }
306             frefedSequence.remove(r);
307             rSize--;
308           }
309           else
310           {
311             System.err
312                     .println("IMPLEMENTATION WARNING: Unresolved forward reference for hash string "
313                             + ref[0]
314                             + " with objecttype "
315                             + ref[1].getClass());
316             r++;
317           }
318         }
319         else
320         {
321           // empty reference
322           frefedSequence.remove(r);
323           rSize--;
324         }
325       }
326     }
327   }
328
329   /**
330    * This maintains a map of viewports, the key being the seqSetId. Important to
331    * set historyItem and redoList for multiple views
332    */
333   Map<String, AlignViewport> viewportsAdded = new HashMap<String, AlignViewport>();
334
335   Map<String, AlignmentAnnotation> annotationIds = new HashMap<String, AlignmentAnnotation>();
336
337   String uniqueSetSuffix = "";
338
339   /**
340    * List of pdbfiles added to Jar
341    */
342   List<String> pdbfiles = null;
343
344   // SAVES SEVERAL ALIGNMENT WINDOWS TO SAME JARFILE
345   public void saveState(File statefile)
346   {
347     FileOutputStream fos = null;
348     try
349     {
350       fos = new FileOutputStream(statefile);
351       JarOutputStream jout = new JarOutputStream(fos);
352       saveState(jout);
353
354     } catch (Exception e)
355     {
356       // TODO: inform user of the problem - they need to know if their data was
357       // not saved !
358       if (errorMessage == null)
359       {
360         errorMessage = "Couldn't write Jalview Archive to output file '"
361                 + statefile + "' - See console error log for details";
362       }
363       else
364       {
365         errorMessage += "(output file was '" + statefile + "')";
366       }
367       e.printStackTrace();
368     } finally
369     {
370       if (fos != null)
371       {
372         try
373         {
374           fos.close();
375         } catch (IOException e)
376         {
377           // ignore
378         }
379       }
380     }
381     reportErrors();
382   }
383
384   /**
385    * Writes a jalview project archive to the given Jar output stream.
386    * 
387    * @param jout
388    */
389   public void saveState(JarOutputStream jout)
390   {
391     AlignFrame[] frames = Desktop.getAlignFrames();
392
393     if (frames == null)
394     {
395       return;
396     }
397
398     Hashtable<String, AlignFrame> dsses = new Hashtable<String, AlignFrame>();
399
400     /*
401      * ensure cached data is clear before starting
402      */
403     // todo tidy up seqRefIds, seqsToIds initialisation / reset
404     rnaSessions.clear();
405     splitFrameCandidates.clear();
406
407     try
408     {
409
410       // NOTE UTF-8 MUST BE USED FOR WRITING UNICODE CHARS
411       // //////////////////////////////////////////////////
412
413       List<String> shortNames = new ArrayList<String>();
414       List<String> viewIds = new ArrayList<String>();
415
416       // REVERSE ORDER
417       for (int i = frames.length - 1; i > -1; i--)
418       {
419         AlignFrame af = frames[i];
420         // skip ?
421         if (skipList != null
422                 && skipList
423                         .containsKey(af.getViewport().getSequenceSetId()))
424         {
425           continue;
426         }
427
428         String shortName = makeFilename(af, shortNames);
429
430         int ap, apSize = af.alignPanels.size();
431
432         for (ap = 0; ap < apSize; ap++)
433         {
434           AlignmentPanel apanel = af.alignPanels.get(ap);
435           String fileName = apSize == 1 ? shortName : ap + shortName;
436           if (!fileName.endsWith(".xml"))
437           {
438             fileName = fileName + ".xml";
439           }
440
441           saveState(apanel, fileName, jout, viewIds);
442
443           String dssid = getDatasetIdRef(af.getViewport().getAlignment()
444                   .getDataset());
445           if (!dsses.containsKey(dssid))
446           {
447             dsses.put(dssid, af);
448           }
449         }
450       }
451
452       writeDatasetFor(dsses, "" + jout.hashCode() + " " + uniqueSetSuffix,
453               jout);
454
455       try
456       {
457         jout.flush();
458       } catch (Exception foo)
459       {
460       }
461       ;
462       jout.close();
463     } catch (Exception ex)
464     {
465       // TODO: inform user of the problem - they need to know if their data was
466       // not saved !
467       if (errorMessage == null)
468       {
469         errorMessage = "Couldn't write Jalview Archive - see error output for details";
470       }
471       ex.printStackTrace();
472     }
473   }
474
475   /**
476    * Generates a distinct file name, based on the title of the AlignFrame, by
477    * appending _n for increasing n until an unused name is generated. The new
478    * name (without its extension) is added to the list.
479    * 
480    * @param af
481    * @param namesUsed
482    * @return the generated name, with .xml extension
483    */
484   protected String makeFilename(AlignFrame af, List<String> namesUsed)
485   {
486     String shortName = af.getTitle();
487
488     if (shortName.indexOf(File.separatorChar) > -1)
489     {
490       shortName = shortName.substring(shortName
491               .lastIndexOf(File.separatorChar) + 1);
492     }
493
494     int count = 1;
495
496     while (namesUsed.contains(shortName))
497     {
498       if (shortName.endsWith("_" + (count - 1)))
499       {
500         shortName = shortName.substring(0, shortName.lastIndexOf("_"));
501       }
502
503       shortName = shortName.concat("_" + count);
504       count++;
505     }
506
507     namesUsed.add(shortName);
508
509     if (!shortName.endsWith(".xml"))
510     {
511       shortName = shortName + ".xml";
512     }
513     return shortName;
514   }
515
516   // USE THIS METHOD TO SAVE A SINGLE ALIGNMENT WINDOW
517   public boolean saveAlignment(AlignFrame af, String jarFile,
518           String fileName)
519   {
520     try
521     {
522       int ap = 0;
523       int apSize = af.alignPanels.size();
524       FileOutputStream fos = new FileOutputStream(jarFile);
525       JarOutputStream jout = new JarOutputStream(fos);
526       Hashtable<String, AlignFrame> dsses = new Hashtable<String, AlignFrame>();
527       List<String> viewIds = new ArrayList<String>();
528
529       for (AlignmentPanel apanel : af.alignPanels)
530       {
531         String jfileName = apSize == 1 ? fileName : fileName + ap;
532         ap++;
533         if (!jfileName.endsWith(".xml"))
534         {
535           jfileName = jfileName + ".xml";
536         }
537         saveState(apanel, jfileName, jout, viewIds);
538         String dssid = getDatasetIdRef(af.getViewport().getAlignment()
539                 .getDataset());
540         if (!dsses.containsKey(dssid))
541         {
542           dsses.put(dssid, af);
543         }
544       }
545       writeDatasetFor(dsses, fileName, jout);
546       try
547       {
548         jout.flush();
549       } catch (Exception foo)
550       {
551       }
552       ;
553       jout.close();
554       return true;
555     } catch (Exception ex)
556     {
557       errorMessage = "Couldn't Write alignment view to Jalview Archive - see error output for details";
558       ex.printStackTrace();
559       return false;
560     }
561   }
562
563   private void writeDatasetFor(Hashtable<String, AlignFrame> dsses,
564           String fileName, JarOutputStream jout)
565   {
566
567     for (String dssids : dsses.keySet())
568     {
569       AlignFrame _af = dsses.get(dssids);
570       String jfileName = fileName + " Dataset for " + _af.getTitle();
571       if (!jfileName.endsWith(".xml"))
572       {
573         jfileName = jfileName + ".xml";
574       }
575       saveState(_af.alignPanel, jfileName, true, jout, null);
576     }
577   }
578
579   /**
580    * create a JalviewModel from an alignment view and marshall it to a
581    * JarOutputStream
582    * 
583    * @param ap
584    *          panel to create jalview model for
585    * @param fileName
586    *          name of alignment panel written to output stream
587    * @param jout
588    *          jar output stream
589    * @param viewIds
590    * @param out
591    *          jar entry name
592    */
593   public JalviewModel saveState(AlignmentPanel ap, String fileName,
594           JarOutputStream jout, List<String> viewIds)
595   {
596     return saveState(ap, fileName, false, jout, viewIds);
597   }
598
599   /**
600    * create a JalviewModel from an alignment view and marshall it to a
601    * JarOutputStream
602    * 
603    * @param ap
604    *          panel to create jalview model for
605    * @param fileName
606    *          name of alignment panel written to output stream
607    * @param storeDS
608    *          when true, only write the dataset for the alignment, not the data
609    *          associated with the view.
610    * @param jout
611    *          jar output stream
612    * @param out
613    *          jar entry name
614    */
615   public JalviewModel saveState(AlignmentPanel ap, String fileName,
616           boolean storeDS, JarOutputStream jout, List<String> viewIds)
617   {
618     if (viewIds == null)
619     {
620       viewIds = new ArrayList<String>();
621     }
622
623     initSeqRefs();
624
625     List<UserColourScheme> userColours = new ArrayList<UserColourScheme>();
626
627     AlignViewport av = ap.av;
628
629     JalviewModel object = new JalviewModel();
630     object.setVamsasModel(new jalview.schemabinding.version2.VamsasModel());
631
632     object.setCreationDate(new java.util.Date(System.currentTimeMillis()));
633     object.setVersion(jalview.bin.Cache.getDefault("VERSION",
634             "Development Build"));
635
636     jalview.datamodel.AlignmentI jal = av.getAlignment();
637
638     if (av.hasHiddenRows())
639     {
640       jal = jal.getHiddenSequences().getFullAlignment();
641     }
642
643     SequenceSet vamsasSet = new SequenceSet();
644     Sequence vamsasSeq;
645     JalviewModelSequence jms = new JalviewModelSequence();
646
647     vamsasSet.setGapChar(jal.getGapCharacter() + "");
648
649     if (jal.getDataset() != null)
650     {
651       // dataset id is the dataset's hashcode
652       vamsasSet.setDatasetId(getDatasetIdRef(jal.getDataset()));
653       if (storeDS)
654       {
655         // switch jal and the dataset
656         jal = jal.getDataset();
657       }
658     }
659     if (jal.getProperties() != null)
660     {
661       Enumeration en = jal.getProperties().keys();
662       while (en.hasMoreElements())
663       {
664         String key = en.nextElement().toString();
665         SequenceSetProperties ssp = new SequenceSetProperties();
666         ssp.setKey(key);
667         ssp.setValue(jal.getProperties().get(key).toString());
668         vamsasSet.addSequenceSetProperties(ssp);
669       }
670     }
671
672     JSeq jseq;
673     Set<String> calcIdSet = new HashSet<String>();
674
675     // SAVE SEQUENCES
676     for (int i = 0; i < jal.getHeight(); i++)
677     {
678       final SequenceI jds = jal.getSequenceAt(i);
679       final SequenceI jdatasq = jds.getDatasetSequence() == null ? jds
680               : jds.getDatasetSequence();
681       String id = seqHash(jds);
682
683       if (seqRefIds.get(id) != null)
684       {
685         // This happens for two reasons: 1. multiple views are being serialised.
686         // 2. the hashCode has collided with another sequence's code. This DOES
687         // HAPPEN! (PF00072.15.stk does this)
688         // JBPNote: Uncomment to debug writing out of files that do not read
689         // back in due to ArrayOutOfBoundExceptions.
690         // System.err.println("vamsasSeq backref: "+id+"");
691         // System.err.println(jds.getName()+"
692         // "+jds.getStart()+"-"+jds.getEnd()+" "+jds.getSequenceAsString());
693         // System.err.println("Hashcode: "+seqHash(jds));
694         // SequenceI rsq = (SequenceI) seqRefIds.get(id + "");
695         // System.err.println(rsq.getName()+"
696         // "+rsq.getStart()+"-"+rsq.getEnd()+" "+rsq.getSequenceAsString());
697         // System.err.println("Hashcode: "+seqHash(rsq));
698       }
699       else
700       {
701         vamsasSeq = createVamsasSequence(id, jds);
702         vamsasSet.addSequence(vamsasSeq);
703         seqRefIds.put(id, jds);
704       }
705
706       jseq = new JSeq();
707       jseq.setStart(jds.getStart());
708       jseq.setEnd(jds.getEnd());
709       jseq.setColour(av.getSequenceColour(jds).getRGB());
710
711       jseq.setId(id); // jseq id should be a string not a number
712       if (!storeDS)
713       {
714         // Store any sequences this sequence represents
715         if (av.hasHiddenRows())
716         {
717           jseq.setHidden(av.getAlignment().getHiddenSequences()
718                   .isHidden(jds));
719
720           if (av.isHiddenRepSequence(jal.getSequenceAt(i)))
721           {
722             jalview.datamodel.SequenceI[] reps = av
723                     .getRepresentedSequences(jal.getSequenceAt(i))
724                     .getSequencesInOrder(jal);
725
726             for (int h = 0; h < reps.length; h++)
727             {
728               if (reps[h] != jal.getSequenceAt(i))
729               {
730                 jseq.addHiddenSequences(jal.findIndex(reps[h]));
731               }
732             }
733           }
734         }
735       }
736
737       if (jds.getSequenceFeatures() != null)
738       {
739         jalview.datamodel.SequenceFeature[] sf = jds.getSequenceFeatures();
740         int index = 0;
741         while (index < sf.length)
742         {
743           Features features = new Features();
744
745           features.setBegin(sf[index].getBegin());
746           features.setEnd(sf[index].getEnd());
747           features.setDescription(sf[index].getDescription());
748           features.setType(sf[index].getType());
749           features.setFeatureGroup(sf[index].getFeatureGroup());
750           features.setScore(sf[index].getScore());
751           if (sf[index].links != null)
752           {
753             for (int l = 0; l < sf[index].links.size(); l++)
754             {
755               OtherData keyValue = new OtherData();
756               keyValue.setKey("LINK_" + l);
757               keyValue.setValue(sf[index].links.elementAt(l).toString());
758               features.addOtherData(keyValue);
759             }
760           }
761           if (sf[index].otherDetails != null)
762           {
763             String key;
764             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           @Override
2182           public void run()
2183           {
2184             setLoadingFinishedForNewStructureViewers();
2185           };
2186         });
2187       } catch (Exception x)
2188       {
2189         System.err.println("Error loading alignment: " + x.getMessage());
2190       }
2191     }
2192     return af;
2193   }
2194
2195   private jarInputStreamProvider createjarInputStreamProvider(
2196           final String file) throws MalformedURLException
2197   {
2198     URL url = null;
2199     errorMessage = null;
2200     uniqueSetSuffix = null;
2201     seqRefIds = null;
2202     viewportsAdded.clear();
2203     frefedSequence = null;
2204
2205     if (file.startsWith("http://"))
2206     {
2207       url = new URL(file);
2208     }
2209     final URL _url = url;
2210     return new jarInputStreamProvider()
2211     {
2212
2213       @Override
2214       public JarInputStream getJarInputStream() throws IOException
2215       {
2216         if (_url != null)
2217         {
2218           return new JarInputStream(_url.openStream());
2219         }
2220         else
2221         {
2222           return new JarInputStream(new FileInputStream(file));
2223         }
2224       }
2225
2226       @Override
2227       public String getFilename()
2228       {
2229         return file;
2230       }
2231     };
2232   }
2233
2234   /**
2235    * Recover jalview session from a jalview project archive. Caller may
2236    * initialise uniqueSetSuffix, seqRefIds, viewportsAdded and frefedSequence
2237    * themselves. Any null fields will be initialised with default values,
2238    * non-null fields are left alone.
2239    * 
2240    * @param jprovider
2241    * @return
2242    */
2243   public AlignFrame loadJalviewAlign(final jarInputStreamProvider jprovider)
2244   {
2245     errorMessage = null;
2246     if (uniqueSetSuffix == null)
2247     {
2248       uniqueSetSuffix = System.currentTimeMillis() % 100000 + "";
2249     }
2250     if (seqRefIds == null)
2251     {
2252       seqRefIds = new HashMap<String, SequenceI>();
2253     }
2254     if (frefedSequence == null)
2255     {
2256       frefedSequence = new Vector<Object[]>();
2257     }
2258
2259     AlignFrame af = null, _af = null;
2260     Map<String, AlignFrame> gatherToThisFrame = new HashMap<String, AlignFrame>();
2261     final String file = jprovider.getFilename();
2262     try
2263     {
2264       JarInputStream jin = null;
2265       JarEntry jarentry = null;
2266       int entryCount = 1;
2267
2268       do
2269       {
2270         jin = jprovider.getJarInputStream();
2271         for (int i = 0; i < entryCount; i++)
2272         {
2273           jarentry = jin.getNextJarEntry();
2274         }
2275
2276         if (jarentry != null && jarentry.getName().endsWith(".xml"))
2277         {
2278           InputStreamReader in = new InputStreamReader(jin, UTF_8);
2279           JalviewModel object = new JalviewModel();
2280
2281           Unmarshaller unmar = new Unmarshaller(object);
2282           unmar.setValidation(false);
2283           object = (JalviewModel) unmar.unmarshal(in);
2284           if (true) // !skipViewport(object))
2285           {
2286             _af = loadFromObject(object, file, true, jprovider);
2287             if (object.getJalviewModelSequence().getViewportCount() > 0)
2288             {
2289               af = _af;
2290               if (af.viewport.isGatherViewsHere())
2291               {
2292                 gatherToThisFrame.put(af.viewport.getSequenceSetId(), af);
2293               }
2294             }
2295           }
2296           entryCount++;
2297         }
2298         else if (jarentry != null)
2299         {
2300           // Some other file here.
2301           entryCount++;
2302         }
2303       } while (jarentry != null);
2304       resolveFrefedSequences();
2305     } catch (IOException ex)
2306     {
2307       ex.printStackTrace();
2308       errorMessage = "Couldn't locate Jalview XML file : " + file;
2309       System.err.println("Exception whilst loading jalview XML file : "
2310               + ex + "\n");
2311     } catch (Exception ex)
2312     {
2313       System.err.println("Parsing as Jalview Version 2 file failed.");
2314       ex.printStackTrace(System.err);
2315       if (attemptversion1parse)
2316       {
2317         // Is Version 1 Jar file?
2318         try
2319         {
2320           af = new Jalview2XML_V1(raiseGUI).LoadJalviewAlign(jprovider);
2321         } catch (Exception ex2)
2322         {
2323           System.err.println("Exception whilst loading as jalviewXMLV1:");
2324           ex2.printStackTrace();
2325           af = null;
2326         }
2327       }
2328       if (Desktop.instance != null)
2329       {
2330         Desktop.instance.stopLoading();
2331       }
2332       if (af != null)
2333       {
2334         System.out.println("Successfully loaded archive file");
2335         return af;
2336       }
2337       ex.printStackTrace();
2338
2339       System.err.println("Exception whilst loading jalview XML file : "
2340               + ex + "\n");
2341     } catch (OutOfMemoryError e)
2342     {
2343       // Don't use the OOM Window here
2344       errorMessage = "Out of memory loading jalview XML file";
2345       System.err.println("Out of memory whilst loading jalview XML file");
2346       e.printStackTrace();
2347     }
2348
2349     if (Desktop.instance != null)
2350     {
2351       Desktop.instance.stopLoading();
2352     }
2353
2354     /*
2355      * Regather multiple views (with the same sequence set id) to the frame (if
2356      * any) that is flagged as the one to gather to, i.e. convert them to tabbed
2357      * views instead of separate frames. Note this doesn't restore a state where
2358      * some expanded views in turn have tabbed views - the last "first tab" read
2359      * in will play the role of gatherer for all.
2360      */
2361     for (AlignFrame fr : gatherToThisFrame.values())
2362     {
2363       Desktop.instance.gatherViews(fr);
2364     }
2365
2366     restoreSplitFrames();
2367
2368     if (errorMessage != null)
2369     {
2370       reportErrors();
2371     }
2372     return af;
2373   }
2374
2375   /**
2376    * Try to reconstruct and display SplitFrame windows, where each contains
2377    * complementary dna and protein alignments. Done by pairing up AlignFrame
2378    * objects (created earlier) which have complementary viewport ids associated.
2379    */
2380   protected void restoreSplitFrames()
2381   {
2382     List<SplitFrame> gatherTo = new ArrayList<SplitFrame>();
2383     List<AlignFrame> addedToSplitFrames = new ArrayList<AlignFrame>();
2384     Map<String, AlignFrame> dna = new HashMap<String, AlignFrame>();
2385
2386     /*
2387      * Identify the DNA alignments
2388      */
2389     for (Entry<Viewport, AlignFrame> candidate : splitFrameCandidates
2390             .entrySet())
2391     {
2392       AlignFrame af = candidate.getValue();
2393       if (af.getViewport().getAlignment().isNucleotide())
2394       {
2395         dna.put(candidate.getKey().getId(), af);
2396       }
2397     }
2398
2399     /*
2400      * Try to match up the protein complements
2401      */
2402     for (Entry<Viewport, AlignFrame> candidate : splitFrameCandidates
2403             .entrySet())
2404     {
2405       AlignFrame af = candidate.getValue();
2406       if (!af.getViewport().getAlignment().isNucleotide())
2407       {
2408         String complementId = candidate.getKey().getComplementId();
2409         // only non-null complements should be in the Map
2410         if (complementId != null && dna.containsKey(complementId))
2411         {
2412           final AlignFrame dnaFrame = dna.get(complementId);
2413           SplitFrame sf = createSplitFrame(dnaFrame, af);
2414           addedToSplitFrames.add(dnaFrame);
2415           addedToSplitFrames.add(af);
2416           if (af.viewport.isGatherViewsHere())
2417           {
2418             gatherTo.add(sf);
2419           }
2420         }
2421       }
2422     }
2423
2424     /*
2425      * Open any that we failed to pair up (which shouldn't happen!) as
2426      * standalone AlignFrame's.
2427      */
2428     for (Entry<Viewport, AlignFrame> candidate : splitFrameCandidates
2429             .entrySet())
2430     {
2431       AlignFrame af = candidate.getValue();
2432       if (!addedToSplitFrames.contains(af))
2433       {
2434         Viewport view = candidate.getKey();
2435         Desktop.addInternalFrame(af, view.getTitle(), view.getWidth(),
2436                 view.getHeight());
2437         System.err.println("Failed to restore view " + view.getTitle()
2438                 + " to split frame");
2439       }
2440     }
2441
2442     /*
2443      * Gather back into tabbed views as flagged.
2444      */
2445     for (SplitFrame sf : gatherTo)
2446     {
2447       Desktop.instance.gatherViews(sf);
2448     }
2449
2450     splitFrameCandidates.clear();
2451   }
2452
2453   /**
2454    * Construct and display one SplitFrame holding DNA and protein alignments.
2455    * 
2456    * @param dnaFrame
2457    * @param proteinFrame
2458    * @return
2459    */
2460   protected SplitFrame createSplitFrame(AlignFrame dnaFrame,
2461           AlignFrame proteinFrame)
2462   {
2463     SplitFrame splitFrame = new SplitFrame(dnaFrame, proteinFrame);
2464     String title = MessageManager.getString("label.linked_view_title");
2465     int width = (int) dnaFrame.getBounds().getWidth();
2466     int height = (int) (dnaFrame.getBounds().getHeight()
2467             + proteinFrame.getBounds().getHeight() + 50);
2468
2469     /*
2470      * SplitFrame location is saved to both enclosed frames
2471      */
2472     splitFrame.setLocation(dnaFrame.getX(), dnaFrame.getY());
2473     Desktop.addInternalFrame(splitFrame, title, width, height);
2474
2475     /*
2476      * And compute cDNA consensus (couldn't do earlier with consensus as
2477      * mappings were not yet present)
2478      */
2479     proteinFrame.viewport.alignmentChanged(proteinFrame.alignPanel);
2480
2481     return splitFrame;
2482   }
2483
2484   /**
2485    * check errorMessage for a valid error message and raise an error box in the
2486    * GUI or write the current errorMessage to stderr and then clear the error
2487    * state.
2488    */
2489   protected void reportErrors()
2490   {
2491     reportErrors(false);
2492   }
2493
2494   protected void reportErrors(final boolean saving)
2495   {
2496     if (errorMessage != null)
2497     {
2498       final String finalErrorMessage = errorMessage;
2499       if (raiseGUI)
2500       {
2501         javax.swing.SwingUtilities.invokeLater(new Runnable()
2502         {
2503           @Override
2504           public void run()
2505           {
2506             JOptionPane.showInternalMessageDialog(Desktop.desktop,
2507                     finalErrorMessage, "Error "
2508                             + (saving ? "saving" : "loading")
2509                             + " Jalview file", JOptionPane.WARNING_MESSAGE);
2510           }
2511         });
2512       }
2513       else
2514       {
2515         System.err.println("Problem loading Jalview file: " + errorMessage);
2516       }
2517     }
2518     errorMessage = null;
2519   }
2520
2521   Map<String, String> alreadyLoadedPDB = new HashMap<String, String>();
2522
2523   /**
2524    * when set, local views will be updated from view stored in JalviewXML
2525    * Currently (28th Sep 2008) things will go horribly wrong in vamsas document
2526    * sync if this is set to true.
2527    */
2528   private final boolean updateLocalViews = false;
2529
2530   /**
2531    * Returns the path to a temporary file holding the PDB file for the given PDB
2532    * id. The first time of asking, searches for a file of that name in the
2533    * Jalview project jar, and copies it to a new temporary file. Any repeat
2534    * requests just return the path to the file previously created.
2535    * 
2536    * @param jprovider
2537    * @param pdbId
2538    * @return
2539    */
2540   String loadPDBFile(jarInputStreamProvider jprovider, String pdbId)
2541   {
2542     if (alreadyLoadedPDB.containsKey(pdbId))
2543     {
2544       return alreadyLoadedPDB.get(pdbId).toString();
2545     }
2546
2547     String tempFile = copyJarEntry(jprovider, pdbId, "jalview_pdb");
2548     if (tempFile != null)
2549     {
2550       alreadyLoadedPDB.put(pdbId, tempFile);
2551     }
2552     return tempFile;
2553   }
2554
2555   /**
2556    * Copies the jar entry of given name to a new temporary file and returns the
2557    * path to the file, or null if the entry is not found.
2558    * 
2559    * @param jprovider
2560    * @param jarEntryName
2561    * @param prefix
2562    *          a prefix for the temporary file name, must be at least three
2563    *          characters long
2564    * @return
2565    */
2566   protected String copyJarEntry(jarInputStreamProvider jprovider,
2567           String jarEntryName, String prefix)
2568   {
2569     BufferedReader in = null;
2570     PrintWriter out = null;
2571
2572     try
2573     {
2574       JarInputStream jin = jprovider.getJarInputStream();
2575       /*
2576        * if (jprovider.startsWith("http://")) { jin = new JarInputStream(new
2577        * URL(jprovider).openStream()); } else { jin = new JarInputStream(new
2578        * FileInputStream(jprovider)); }
2579        */
2580
2581       JarEntry entry = null;
2582       do
2583       {
2584         entry = jin.getNextJarEntry();
2585       } while (entry != null && !entry.getName().equals(jarEntryName));
2586       if (entry != null)
2587       {
2588         in = new BufferedReader(new InputStreamReader(jin, UTF_8));
2589         File outFile = File.createTempFile(prefix, ".tmp");
2590         outFile.deleteOnExit();
2591         out = new PrintWriter(new FileOutputStream(outFile));
2592         String data;
2593
2594         while ((data = in.readLine()) != null)
2595         {
2596           out.println(data);
2597         }
2598         out.flush();
2599         String t = outFile.getAbsolutePath();
2600         return t;
2601       }
2602       else
2603       {
2604         warn("Couldn't find entry in Jalview Jar for " + jarEntryName);
2605       }
2606     } catch (Exception ex)
2607     {
2608       ex.printStackTrace();
2609     } finally
2610     {
2611       if (in != null)
2612       {
2613         try
2614         {
2615           in.close();
2616         } catch (IOException e)
2617         {
2618           // ignore
2619         }
2620       }
2621       if (out != null)
2622       {
2623         out.close();
2624       }
2625     }
2626
2627     return null;
2628   }
2629
2630   private class JvAnnotRow
2631   {
2632     public JvAnnotRow(int i, AlignmentAnnotation jaa)
2633     {
2634       order = i;
2635       template = jaa;
2636     }
2637
2638     /**
2639      * persisted version of annotation row from which to take vis properties
2640      */
2641     public jalview.datamodel.AlignmentAnnotation template;
2642
2643     /**
2644      * original position of the annotation row in the alignment
2645      */
2646     public int order;
2647   }
2648
2649   /**
2650    * Load alignment frame from jalview XML DOM object
2651    * 
2652    * @param object
2653    *          DOM
2654    * @param file
2655    *          filename source string
2656    * @param loadTreesAndStructures
2657    *          when false only create Viewport
2658    * @param jprovider
2659    *          data source provider
2660    * @return alignment frame created from view stored in DOM
2661    */
2662   AlignFrame loadFromObject(JalviewModel object, String file,
2663           boolean loadTreesAndStructures, jarInputStreamProvider jprovider)
2664   {
2665     SequenceSet vamsasSet = object.getVamsasModel().getSequenceSet(0);
2666     Sequence[] vamsasSeq = vamsasSet.getSequence();
2667
2668     JalviewModelSequence jms = object.getJalviewModelSequence();
2669
2670     Viewport view = (jms.getViewportCount() > 0) ? jms.getViewport(0)
2671             : null;
2672
2673     // ////////////////////////////////
2674     // LOAD SEQUENCES
2675
2676     List<SequenceI> hiddenSeqs = null;
2677     jalview.datamodel.Sequence jseq;
2678
2679     List<SequenceI> tmpseqs = new ArrayList<SequenceI>();
2680
2681     boolean multipleView = false;
2682
2683     JSeq[] jseqs = object.getJalviewModelSequence().getJSeq();
2684     int vi = 0; // counter in vamsasSeq array
2685     for (int i = 0; i < jseqs.length; i++)
2686     {
2687       String seqId = jseqs[i].getId();
2688
2689       if (seqRefIds.get(seqId) != null)
2690       {
2691         tmpseqs.add(seqRefIds.get(seqId));
2692         multipleView = true;
2693       }
2694       else
2695       {
2696         jseq = new jalview.datamodel.Sequence(vamsasSeq[vi].getName(),
2697                 vamsasSeq[vi].getSequence());
2698         jseq.setDescription(vamsasSeq[vi].getDescription());
2699         jseq.setStart(jseqs[i].getStart());
2700         jseq.setEnd(jseqs[i].getEnd());
2701         jseq.setVamsasId(uniqueSetSuffix + seqId);
2702         seqRefIds.put(vamsasSeq[vi].getId(), jseq);
2703         tmpseqs.add(jseq);
2704         vi++;
2705       }
2706
2707       if (jseqs[i].getHidden())
2708       {
2709         if (hiddenSeqs == null)
2710         {
2711           hiddenSeqs = new ArrayList<SequenceI>();
2712         }
2713
2714         hiddenSeqs.add(seqRefIds.get(seqId));
2715       }
2716
2717     }
2718
2719     // /
2720     // Create the alignment object from the sequence set
2721     // ///////////////////////////////
2722     SequenceI[] orderedSeqs = tmpseqs
2723             .toArray(new SequenceI[tmpseqs.size()]);
2724
2725     Alignment al = new Alignment(orderedSeqs);
2726
2727     // / Add the alignment properties
2728     for (int i = 0; i < vamsasSet.getSequenceSetPropertiesCount(); i++)
2729     {
2730       SequenceSetProperties ssp = vamsasSet.getSequenceSetProperties(i);
2731       al.setProperty(ssp.getKey(), ssp.getValue());
2732     }
2733
2734     // /
2735     // SequenceFeatures are added to the DatasetSequence,
2736     // so we must create or recover the dataset before loading features
2737     // ///////////////////////////////
2738     if (vamsasSet.getDatasetId() == null || vamsasSet.getDatasetId() == "")
2739     {
2740       // older jalview projects do not have a dataset id.
2741       al.setDataset(null);
2742     }
2743     else
2744     {
2745       // recover dataset - passing on flag indicating if this a 'viewless'
2746       // sequence set (a.k.a. a stored dataset for the project)
2747       recoverDatasetFor(vamsasSet, al, object.getJalviewModelSequence()
2748               .getViewportCount() == 0);
2749     }
2750     // ///////////////////////////////
2751
2752     Hashtable pdbloaded = new Hashtable(); // TODO nothing writes to this??
2753     if (!multipleView)
2754     {
2755       // load sequence features, database references and any associated PDB
2756       // structures for the alignment
2757       for (int i = 0; i < vamsasSeq.length; i++)
2758       {
2759         if (jseqs[i].getFeaturesCount() > 0)
2760         {
2761           Features[] features = jseqs[i].getFeatures();
2762           for (int f = 0; f < features.length; f++)
2763           {
2764             jalview.datamodel.SequenceFeature sf = new jalview.datamodel.SequenceFeature(
2765                     features[f].getType(), features[f].getDescription(),
2766                     features[f].getStatus(), features[f].getBegin(),
2767                     features[f].getEnd(), features[f].getFeatureGroup());
2768
2769             sf.setScore(features[f].getScore());
2770             for (int od = 0; od < features[f].getOtherDataCount(); od++)
2771             {
2772               OtherData keyValue = features[f].getOtherData(od);
2773               if (keyValue.getKey().startsWith("LINK"))
2774               {
2775                 sf.addLink(keyValue.getValue());
2776               }
2777               else
2778               {
2779                 sf.setValue(keyValue.getKey(), keyValue.getValue());
2780               }
2781
2782             }
2783
2784             al.getSequenceAt(i).getDatasetSequence().addSequenceFeature(sf);
2785           }
2786         }
2787         if (vamsasSeq[i].getDBRefCount() > 0)
2788         {
2789           addDBRefs(al.getSequenceAt(i).getDatasetSequence(), vamsasSeq[i]);
2790         }
2791         if (jseqs[i].getPdbidsCount() > 0)
2792         {
2793           Pdbids[] ids = jseqs[i].getPdbids();
2794           for (int p = 0; p < ids.length; p++)
2795           {
2796             jalview.datamodel.PDBEntry entry = new jalview.datamodel.PDBEntry();
2797             entry.setId(ids[p].getId());
2798             if (ids[p].getType() != null)
2799             {
2800               if (ids[p].getType().equalsIgnoreCase("PDB"))
2801               {
2802                 entry.setType(PDBEntry.Type.PDB);
2803               }
2804               else
2805               {
2806                 entry.setType(PDBEntry.Type.FILE);
2807               }
2808             }
2809             if (ids[p].getFile() != null)
2810             {
2811               if (!pdbloaded.containsKey(ids[p].getFile()))
2812               {
2813                 entry.setFile(loadPDBFile(jprovider, ids[p].getId()));
2814               }
2815               else
2816               {
2817                 entry.setFile(pdbloaded.get(ids[p].getId()).toString());
2818               }
2819             }
2820             StructureSelectionManager.getStructureSelectionManager(
2821                     Desktop.instance).registerPDBEntry(entry);
2822             al.getSequenceAt(i).getDatasetSequence().addPDBId(entry);
2823           }
2824         }
2825       }
2826     } // end !multipleview
2827
2828     // ///////////////////////////////
2829     // LOAD SEQUENCE MAPPINGS
2830
2831     if (vamsasSet.getAlcodonFrameCount() > 0)
2832     {
2833       // TODO Potentially this should only be done once for all views of an
2834       // alignment
2835       AlcodonFrame[] alc = vamsasSet.getAlcodonFrame();
2836       for (int i = 0; i < alc.length; i++)
2837       {
2838         AlignedCodonFrame cf = new AlignedCodonFrame();
2839         if (alc[i].getAlcodMapCount() > 0)
2840         {
2841           AlcodMap[] maps = alc[i].getAlcodMap();
2842           for (int m = 0; m < maps.length; m++)
2843           {
2844             SequenceI dnaseq = seqRefIds.get(maps[m].getDnasq());
2845             // Load Mapping
2846             jalview.datamodel.Mapping mapping = null;
2847             // attach to dna sequence reference.
2848             if (maps[m].getMapping() != null)
2849             {
2850               mapping = addMapping(maps[m].getMapping());
2851             }
2852             if (dnaseq != null && mapping.getTo() != null)
2853             {
2854               cf.addMap(dnaseq, mapping.getTo(), mapping.getMap());
2855             }
2856             else
2857             {
2858               // defer to later
2859               frefedSequence.add(new Object[] { maps[m].getDnasq(), cf,
2860                   mapping });
2861             }
2862           }
2863         }
2864         al.addCodonFrame(cf);
2865       }
2866     }
2867
2868     // ////////////////////////////////
2869     // LOAD ANNOTATIONS
2870     List<JvAnnotRow> autoAlan = new ArrayList<JvAnnotRow>();
2871
2872     /*
2873      * store any annotations which forward reference a group's ID
2874      */
2875     Map<String, List<AlignmentAnnotation>> groupAnnotRefs = new Hashtable<String, List<AlignmentAnnotation>>();
2876
2877     if (vamsasSet.getAnnotationCount() > 0)
2878     {
2879       Annotation[] an = vamsasSet.getAnnotation();
2880
2881       for (int i = 0; i < an.length; i++)
2882       {
2883         Annotation annotation = an[i];
2884
2885         /**
2886          * test if annotation is automatically calculated for this view only
2887          */
2888         boolean autoForView = false;
2889         if (annotation.getLabel().equals("Quality")
2890                 || annotation.getLabel().equals("Conservation")
2891                 || annotation.getLabel().equals("Consensus"))
2892         {
2893           // Kludge for pre 2.5 projects which lacked the autocalculated flag
2894           autoForView = true;
2895           if (!annotation.hasAutoCalculated())
2896           {
2897             annotation.setAutoCalculated(true);
2898           }
2899         }
2900         if (autoForView
2901                 || (annotation.hasAutoCalculated() && annotation
2902                         .isAutoCalculated()))
2903         {
2904           // remove ID - we don't recover annotation from other views for
2905           // view-specific annotation
2906           annotation.setId(null);
2907         }
2908
2909         // set visiblity for other annotation in this view
2910         String annotationId = annotation.getId();
2911         if (annotationId != null && annotationIds.containsKey(annotationId))
2912         {
2913           AlignmentAnnotation jda = annotationIds.get(annotationId);
2914           // in principle Visible should always be true for annotation displayed
2915           // in multiple views
2916           if (annotation.hasVisible())
2917           {
2918             jda.visible = annotation.getVisible();
2919           }
2920
2921           al.addAnnotation(jda);
2922
2923           continue;
2924         }
2925         // Construct new annotation from model.
2926         AnnotationElement[] ae = annotation.getAnnotationElement();
2927         jalview.datamodel.Annotation[] anot = null;
2928         java.awt.Color firstColour = null;
2929         int anpos;
2930         if (!annotation.getScoreOnly())
2931         {
2932           anot = new jalview.datamodel.Annotation[al.getWidth()];
2933           for (int aa = 0; aa < ae.length && aa < anot.length; aa++)
2934           {
2935             anpos = ae[aa].getPosition();
2936
2937             if (anpos >= anot.length)
2938             {
2939               continue;
2940             }
2941
2942             anot[anpos] = new jalview.datamodel.Annotation(
2943
2944             ae[aa].getDisplayCharacter(), ae[aa].getDescription(),
2945                     (ae[aa].getSecondaryStructure() == null || ae[aa]
2946                             .getSecondaryStructure().length() == 0) ? ' '
2947                             : ae[aa].getSecondaryStructure().charAt(0),
2948                     ae[aa].getValue()
2949
2950             );
2951             // JBPNote: Consider verifying dataflow for IO of secondary
2952             // structure annotation read from Stockholm files
2953             // this was added to try to ensure that
2954             // if (anot[ae[aa].getPosition()].secondaryStructure>' ')
2955             // {
2956             // anot[ae[aa].getPosition()].displayCharacter = "";
2957             // }
2958             anot[anpos].colour = new java.awt.Color(ae[aa].getColour());
2959             if (firstColour == null)
2960             {
2961               firstColour = anot[anpos].colour;
2962             }
2963           }
2964         }
2965         jalview.datamodel.AlignmentAnnotation jaa = null;
2966
2967         if (annotation.getGraph())
2968         {
2969           float llim = 0, hlim = 0;
2970           // if (autoForView || an[i].isAutoCalculated()) {
2971           // hlim=11f;
2972           // }
2973           jaa = new jalview.datamodel.AlignmentAnnotation(
2974                   annotation.getLabel(), annotation.getDescription(), anot,
2975                   llim, hlim, annotation.getGraphType());
2976
2977           jaa.graphGroup = annotation.getGraphGroup();
2978           jaa._linecolour = firstColour;
2979           if (annotation.getThresholdLine() != null)
2980           {
2981             jaa.setThreshold(new jalview.datamodel.GraphLine(annotation
2982                     .getThresholdLine().getValue(), annotation
2983                     .getThresholdLine().getLabel(), new java.awt.Color(
2984                     annotation.getThresholdLine().getColour())));
2985
2986           }
2987           if (autoForView || annotation.isAutoCalculated())
2988           {
2989             // Hardwire the symbol display line to ensure that labels for
2990             // histograms are displayed
2991             jaa.hasText = true;
2992           }
2993         }
2994         else
2995         {
2996           jaa = new jalview.datamodel.AlignmentAnnotation(an[i].getLabel(),
2997                   an[i].getDescription(), anot);
2998           jaa._linecolour = firstColour;
2999         }
3000         // register new annotation
3001         if (an[i].getId() != null)
3002         {
3003           annotationIds.put(an[i].getId(), jaa);
3004           jaa.annotationId = an[i].getId();
3005         }
3006         // recover sequence association
3007         String sequenceRef = an[i].getSequenceRef();
3008         if (sequenceRef != null)
3009         {
3010           // from 2.9 sequenceRef is to sequence id (JAL-1781)
3011           SequenceI sequence = seqRefIds.get(sequenceRef);
3012           if (sequence == null)
3013           {
3014             // in pre-2.9 projects sequence ref is to sequence name
3015             sequence = al.findName(sequenceRef);
3016           }
3017           if (sequence != null)
3018           {
3019             jaa.createSequenceMapping(sequence, 1, true);
3020             sequence.addAlignmentAnnotation(jaa);
3021           }
3022         }
3023         // and make a note of any group association
3024         if (an[i].getGroupRef() != null && an[i].getGroupRef().length() > 0)
3025         {
3026           List<jalview.datamodel.AlignmentAnnotation> aal = groupAnnotRefs
3027                   .get(an[i].getGroupRef());
3028           if (aal == null)
3029           {
3030             aal = new ArrayList<jalview.datamodel.AlignmentAnnotation>();
3031             groupAnnotRefs.put(an[i].getGroupRef(), aal);
3032           }
3033           aal.add(jaa);
3034         }
3035
3036         if (an[i].hasScore())
3037         {
3038           jaa.setScore(an[i].getScore());
3039         }
3040         if (an[i].hasVisible())
3041         {
3042           jaa.visible = an[i].getVisible();
3043         }
3044
3045         if (an[i].hasCentreColLabels())
3046         {
3047           jaa.centreColLabels = an[i].getCentreColLabels();
3048         }
3049
3050         if (an[i].hasScaleColLabels())
3051         {
3052           jaa.scaleColLabel = an[i].getScaleColLabels();
3053         }
3054         if (an[i].hasAutoCalculated() && an[i].isAutoCalculated())
3055         {
3056           // newer files have an 'autoCalculated' flag and store calculation
3057           // state in viewport properties
3058           jaa.autoCalculated = true; // means annotation will be marked for
3059           // update at end of load.
3060         }
3061         if (an[i].hasGraphHeight())
3062         {
3063           jaa.graphHeight = an[i].getGraphHeight();
3064         }
3065         if (an[i].hasBelowAlignment())
3066         {
3067           jaa.belowAlignment = an[i].isBelowAlignment();
3068         }
3069         jaa.setCalcId(an[i].getCalcId());
3070         if (an[i].getPropertyCount() > 0)
3071         {
3072           for (jalview.schemabinding.version2.Property prop : an[i]
3073                   .getProperty())
3074           {
3075             jaa.setProperty(prop.getName(), prop.getValue());
3076           }
3077         }
3078         if (jaa.autoCalculated)
3079         {
3080           autoAlan.add(new JvAnnotRow(i, jaa));
3081         }
3082         else
3083         // if (!autoForView)
3084         {
3085           // add autocalculated group annotation and any user created annotation
3086           // for the view
3087           al.addAnnotation(jaa);
3088         }
3089       }
3090     }
3091     // ///////////////////////
3092     // LOAD GROUPS
3093     // Create alignment markup and styles for this view
3094     if (jms.getJGroupCount() > 0)
3095     {
3096       JGroup[] groups = jms.getJGroup();
3097       boolean addAnnotSchemeGroup = false;
3098       for (int i = 0; i < groups.length; i++)
3099       {
3100         JGroup jGroup = groups[i];
3101         ColourSchemeI cs = null;
3102         if (jGroup.getColour() != null)
3103         {
3104           if (jGroup.getColour().startsWith("ucs"))
3105           {
3106             cs = getUserColourScheme(jms, jGroup.getColour());
3107           }
3108           else if (jGroup.getColour().equals("AnnotationColourGradient")
3109                   && jGroup.getAnnotationColours() != null)
3110           {
3111             addAnnotSchemeGroup = true;
3112             cs = null;
3113           }
3114           else
3115           {
3116             cs = ColourSchemeProperty.getColour(al, jGroup.getColour());
3117           }
3118
3119           if (cs != null)
3120           {
3121             cs.setThreshold(jGroup.getPidThreshold(), true);
3122           }
3123         }
3124
3125         Vector<SequenceI> seqs = new Vector<SequenceI>();
3126
3127         for (int s = 0; s < jGroup.getSeqCount(); s++)
3128         {
3129           String seqId = jGroup.getSeq(s) + "";
3130           SequenceI ts = seqRefIds.get(seqId);
3131
3132           if (ts != null)
3133           {
3134             seqs.addElement(ts);
3135           }
3136         }
3137
3138         if (seqs.size() < 1)
3139         {
3140           continue;
3141         }
3142
3143         SequenceGroup sg = new SequenceGroup(seqs, jGroup.getName(), cs,
3144                 jGroup.getDisplayBoxes(), jGroup.getDisplayText(),
3145                 jGroup.getColourText(), jGroup.getStart(), jGroup.getEnd());
3146
3147         sg.setOutlineColour(new java.awt.Color(jGroup.getOutlineColour()));
3148
3149         sg.textColour = new java.awt.Color(jGroup.getTextCol1());
3150         sg.textColour2 = new java.awt.Color(jGroup.getTextCol2());
3151         sg.setShowNonconserved(jGroup.hasShowUnconserved() ? jGroup
3152                 .isShowUnconserved() : false);
3153         sg.thresholdTextColour = jGroup.getTextColThreshold();
3154         if (jGroup.hasShowConsensusHistogram())
3155         {
3156           sg.setShowConsensusHistogram(jGroup.isShowConsensusHistogram());
3157         }
3158         ;
3159         if (jGroup.hasShowSequenceLogo())
3160         {
3161           sg.setshowSequenceLogo(jGroup.isShowSequenceLogo());
3162         }
3163         if (jGroup.hasNormaliseSequenceLogo())
3164         {
3165           sg.setNormaliseSequenceLogo(jGroup.isNormaliseSequenceLogo());
3166         }
3167         if (jGroup.hasIgnoreGapsinConsensus())
3168         {
3169           sg.setIgnoreGapsConsensus(jGroup.getIgnoreGapsinConsensus());
3170         }
3171         if (jGroup.getConsThreshold() != 0)
3172         {
3173           jalview.analysis.Conservation c = new jalview.analysis.Conservation(
3174                   "All", ResidueProperties.propHash, 3,
3175                   sg.getSequences(null), 0, sg.getWidth() - 1);
3176           c.calculate();
3177           c.verdict(false, 25);
3178           sg.cs.setConservation(c);
3179         }
3180
3181         if (jGroup.getId() != null && groupAnnotRefs.size() > 0)
3182         {
3183           // re-instate unique group/annotation row reference
3184           List<AlignmentAnnotation> jaal = groupAnnotRefs.get(jGroup
3185                   .getId());
3186           if (jaal != null)
3187           {
3188             for (AlignmentAnnotation jaa : jaal)
3189             {
3190               jaa.groupRef = sg;
3191               if (jaa.autoCalculated)
3192               {
3193                 // match up and try to set group autocalc alignment row for this
3194                 // annotation
3195                 if (jaa.label.startsWith("Consensus for "))
3196                 {
3197                   sg.setConsensus(jaa);
3198                 }
3199                 // match up and try to set group autocalc alignment row for this
3200                 // annotation
3201                 if (jaa.label.startsWith("Conservation for "))
3202                 {
3203                   sg.setConservationRow(jaa);
3204                 }
3205               }
3206             }
3207           }
3208         }
3209         al.addGroup(sg);
3210         if (addAnnotSchemeGroup)
3211         {
3212           // reconstruct the annotation colourscheme
3213           sg.cs = constructAnnotationColour(jGroup.getAnnotationColours(),
3214                   null, al, jms, false);
3215         }
3216       }
3217     }
3218     if (view == null)
3219     {
3220       // only dataset in this model, so just return.
3221       return null;
3222     }
3223     // ///////////////////////////////
3224     // LOAD VIEWPORT
3225
3226     // If we just load in the same jar file again, the sequenceSetId
3227     // will be the same, and we end up with multiple references
3228     // to the same sequenceSet. We must modify this id on load
3229     // so that each load of the file gives a unique id
3230     String uniqueSeqSetId = view.getSequenceSetId() + uniqueSetSuffix;
3231     String viewId = (view.getId() == null ? null : view.getId()
3232             + uniqueSetSuffix);
3233     AlignFrame af = null;
3234     AlignViewport av = null;
3235     // now check to see if we really need to create a new viewport.
3236     if (multipleView && viewportsAdded.size() == 0)
3237     {
3238       // We recovered an alignment for which a viewport already exists.
3239       // TODO: fix up any settings necessary for overlaying stored state onto
3240       // state recovered from another document. (may not be necessary).
3241       // we may need a binding from a viewport in memory to one recovered from
3242       // XML.
3243       // and then recover its containing af to allow the settings to be applied.
3244       // TODO: fix for vamsas demo
3245       System.err
3246               .println("About to recover a viewport for existing alignment: Sequence set ID is "
3247                       + uniqueSeqSetId);
3248       Object seqsetobj = retrieveExistingObj(uniqueSeqSetId);
3249       if (seqsetobj != null)
3250       {
3251         if (seqsetobj instanceof String)
3252         {
3253           uniqueSeqSetId = (String) seqsetobj;
3254           System.err
3255                   .println("Recovered extant sequence set ID mapping for ID : New Sequence set ID is "
3256                           + uniqueSeqSetId);
3257         }
3258         else
3259         {
3260           System.err
3261                   .println("Warning : Collision between sequence set ID string and existing jalview object mapping.");
3262         }
3263
3264       }
3265     }
3266     /**
3267      * indicate that annotation colours are applied across all groups (pre
3268      * Jalview 2.8.1 behaviour)
3269      */
3270     boolean doGroupAnnColour = Jalview2XML.isVersionStringLaterThan(
3271             "2.8.1", object.getVersion());
3272
3273     AlignmentPanel ap = null;
3274     boolean isnewview = true;
3275     if (viewId != null)
3276     {
3277       // Check to see if this alignment already has a view id == viewId
3278       jalview.gui.AlignmentPanel views[] = Desktop
3279               .getAlignmentPanels(uniqueSeqSetId);
3280       if (views != null && views.length > 0)
3281       {
3282         for (int v = 0; v < views.length; v++)
3283         {
3284           if (views[v].av.getViewId().equalsIgnoreCase(viewId))
3285           {
3286             // recover the existing alignpanel, alignframe, viewport
3287             af = views[v].alignFrame;
3288             av = views[v].av;
3289             ap = views[v];
3290             // TODO: could even skip resetting view settings if we don't want to
3291             // change the local settings from other jalview processes
3292             isnewview = false;
3293           }
3294         }
3295       }
3296     }
3297
3298     if (isnewview)
3299     {
3300       af = loadViewport(file, jseqs, hiddenSeqs, al, jms, view,
3301               uniqueSeqSetId, viewId, autoAlan);
3302       av = af.viewport;
3303       ap = af.alignPanel;
3304     }
3305
3306     /*
3307      * Load any trees, PDB structures and viewers
3308      * 
3309      * Not done if flag is false (when this method is used for New View)
3310      */
3311     if (loadTreesAndStructures)
3312     {
3313       loadTrees(jms, view, af, av, ap);
3314       loadPDBStructures(jprovider, jseqs, af, ap);
3315       loadRnaViewers(jprovider, jseqs, ap);
3316     }
3317     // and finally return.
3318     return af;
3319   }
3320
3321   /**
3322    * Instantiate and link any saved RNA (Varna) viewers. The state of the Varna
3323    * panel is restored from separate jar entries, two (gapped and trimmed) per
3324    * sequence and secondary structure.
3325    * 
3326    * Currently each viewer shows just one sequence and structure (gapped and
3327    * trimmed), however this method is designed to support multiple sequences or
3328    * structures in viewers if wanted in future.
3329    * 
3330    * @param jprovider
3331    * @param jseqs
3332    * @param ap
3333    */
3334   private void loadRnaViewers(jarInputStreamProvider jprovider,
3335           JSeq[] jseqs, AlignmentPanel ap)
3336   {
3337     /*
3338      * scan the sequences for references to viewers; create each one the first
3339      * time it is referenced, add Rna models to existing viewers
3340      */
3341     for (JSeq jseq : jseqs)
3342     {
3343       for (int i = 0; i < jseq.getRnaViewerCount(); i++)
3344       {
3345         RnaViewer viewer = jseq.getRnaViewer(i);
3346         AppVarna appVarna = findOrCreateVarnaViewer(viewer,
3347                 uniqueSetSuffix, ap);
3348
3349         for (int j = 0; j < viewer.getSecondaryStructureCount(); j++)
3350         {
3351           SecondaryStructure ss = viewer.getSecondaryStructure(j);
3352           SequenceI seq = seqRefIds.get(jseq.getId());
3353           AlignmentAnnotation ann = this.annotationIds.get(ss
3354                   .getAnnotationId());
3355
3356           /*
3357            * add the structure to the Varna display (with session state copied
3358            * from the jar to a temporary file)
3359            */
3360           boolean gapped = ss.isGapped();
3361           String rnaTitle = ss.getTitle();
3362           String sessionState = ss.getViewerState();
3363           String tempStateFile = copyJarEntry(jprovider, sessionState,
3364                   "varna");
3365           RnaModel rna = new RnaModel(rnaTitle, ann, seq, null, gapped);
3366           appVarna.addModelSession(rna, rnaTitle, tempStateFile);
3367         }
3368         appVarna.setInitialSelection(viewer.getSelectedRna());
3369       }
3370     }
3371   }
3372
3373   /**
3374    * Locate and return an already instantiated matching AppVarna, or create one
3375    * if not found
3376    * 
3377    * @param viewer
3378    * @param viewIdSuffix
3379    * @param ap
3380    * @return
3381    */
3382   protected AppVarna findOrCreateVarnaViewer(RnaViewer viewer,
3383           String viewIdSuffix, AlignmentPanel ap)
3384   {
3385     /*
3386      * on each load a suffix is appended to the saved viewId, to avoid conflicts
3387      * if load is repeated
3388      */
3389     String postLoadId = viewer.getViewId() + viewIdSuffix;
3390     for (JInternalFrame frame : getAllFrames())
3391     {
3392       if (frame instanceof AppVarna)
3393       {
3394         AppVarna varna = (AppVarna) frame;
3395         if (postLoadId.equals(varna.getViewId()))
3396         {
3397           // this viewer is already instantiated
3398           // could in future here add ap as another 'parent' of the
3399           // AppVarna window; currently just 1-to-many
3400           return varna;
3401         }
3402       }
3403     }
3404
3405     /*
3406      * viewer not found - make it
3407      */
3408     RnaViewerModel model = new RnaViewerModel(postLoadId,
3409             viewer.getTitle(), viewer.getXpos(), viewer.getYpos(),
3410             viewer.getWidth(), viewer.getHeight(),
3411             viewer.getDividerLocation());
3412     AppVarna varna = new AppVarna(model, ap);
3413
3414     return varna;
3415   }
3416
3417   /**
3418    * Load any saved trees
3419    * 
3420    * @param jms
3421    * @param view
3422    * @param af
3423    * @param av
3424    * @param ap
3425    */
3426   protected void loadTrees(JalviewModelSequence jms, Viewport view,
3427           AlignFrame af, AlignViewport av, AlignmentPanel ap)
3428   {
3429     // TODO result of automated refactoring - are all these parameters needed?
3430     try
3431     {
3432       for (int t = 0; t < jms.getTreeCount(); t++)
3433       {
3434
3435         Tree tree = jms.getTree(t);
3436
3437         TreePanel tp = (TreePanel) retrieveExistingObj(tree.getId());
3438         if (tp == null)
3439         {
3440           tp = af.ShowNewickTree(
3441                   new jalview.io.NewickFile(tree.getNewick()),
3442                   tree.getTitle(), tree.getWidth(), tree.getHeight(),
3443                   tree.getXpos(), tree.getYpos());
3444           if (tree.getId() != null)
3445           {
3446             // perhaps bind the tree id to something ?
3447           }
3448         }
3449         else
3450         {
3451           // update local tree attributes ?
3452           // TODO: should check if tp has been manipulated by user - if so its
3453           // settings shouldn't be modified
3454           tp.setTitle(tree.getTitle());
3455           tp.setBounds(new Rectangle(tree.getXpos(), tree.getYpos(), tree
3456                   .getWidth(), tree.getHeight()));
3457           tp.av = av; // af.viewport; // TODO: verify 'associate with all
3458           // views'
3459           // works still
3460           tp.treeCanvas.av = av; // af.viewport;
3461           tp.treeCanvas.ap = ap; // af.alignPanel;
3462
3463         }
3464         if (tp == null)
3465         {
3466           warn("There was a problem recovering stored Newick tree: \n"
3467                   + tree.getNewick());
3468           continue;
3469         }
3470
3471         tp.fitToWindow.setState(tree.getFitToWindow());
3472         tp.fitToWindow_actionPerformed(null);
3473
3474         if (tree.getFontName() != null)
3475         {
3476           tp.setTreeFont(new java.awt.Font(tree.getFontName(), tree
3477                   .getFontStyle(), tree.getFontSize()));
3478         }
3479         else
3480         {
3481           tp.setTreeFont(new java.awt.Font(view.getFontName(), view
3482                   .getFontStyle(), tree.getFontSize()));
3483         }
3484
3485         tp.showPlaceholders(tree.getMarkUnlinked());
3486         tp.showBootstrap(tree.getShowBootstrap());
3487         tp.showDistances(tree.getShowDistances());
3488
3489         tp.treeCanvas.threshold = tree.getThreshold();
3490
3491         if (tree.getCurrentTree())
3492         {
3493           af.viewport.setCurrentTree(tp.getTree());
3494         }
3495       }
3496
3497     } catch (Exception ex)
3498     {
3499       ex.printStackTrace();
3500     }
3501   }
3502
3503   /**
3504    * Load and link any saved structure viewers.
3505    * 
3506    * @param jprovider
3507    * @param jseqs
3508    * @param af
3509    * @param ap
3510    */
3511   protected void loadPDBStructures(jarInputStreamProvider jprovider,
3512           JSeq[] jseqs, AlignFrame af, AlignmentPanel ap)
3513   {
3514     /*
3515      * Run through all PDB ids on the alignment, and collect mappings between
3516      * distinct view ids and all sequences referring to that view.
3517      */
3518     Map<String, StructureViewerModel> structureViewers = new LinkedHashMap<String, StructureViewerModel>();
3519
3520     for (int i = 0; i < jseqs.length; i++)
3521     {
3522       if (jseqs[i].getPdbidsCount() > 0)
3523       {
3524         Pdbids[] ids = jseqs[i].getPdbids();
3525         for (int p = 0; p < ids.length; p++)
3526         {
3527           final int structureStateCount = ids[p].getStructureStateCount();
3528           for (int s = 0; s < structureStateCount; s++)
3529           {
3530             // check to see if we haven't already created this structure view
3531             final StructureState structureState = ids[p]
3532                     .getStructureState(s);
3533             String sviewid = (structureState.getViewId() == null) ? null
3534                     : structureState.getViewId() + uniqueSetSuffix;
3535             jalview.datamodel.PDBEntry jpdb = new jalview.datamodel.PDBEntry();
3536             // Originally : ids[p].getFile()
3537             // : TODO: verify external PDB file recovery still works in normal
3538             // jalview project load
3539             jpdb.setFile(loadPDBFile(jprovider, ids[p].getId()));
3540             jpdb.setId(ids[p].getId());
3541
3542             int x = structureState.getXpos();
3543             int y = structureState.getYpos();
3544             int width = structureState.getWidth();
3545             int height = structureState.getHeight();
3546
3547             // Probably don't need to do this anymore...
3548             // Desktop.desktop.getComponentAt(x, y);
3549             // TODO: NOW: check that this recovers the PDB file correctly.
3550             String pdbFile = loadPDBFile(jprovider, ids[p].getId());
3551             jalview.datamodel.SequenceI seq = seqRefIds.get(jseqs[i]
3552                     .getId() + "");
3553             if (sviewid == null)
3554             {
3555               sviewid = "_jalview_pre2_4_" + x + "," + y + "," + width
3556                       + "," + height;
3557             }
3558             if (!structureViewers.containsKey(sviewid))
3559             {
3560               structureViewers.put(sviewid,
3561                       new StructureViewerModel(x, y, width, height, false,
3562                               false, true, structureState.getViewId(),
3563                               structureState.getType()));
3564               // Legacy pre-2.7 conversion JAL-823 :
3565               // do not assume any view has to be linked for colour by
3566               // sequence
3567             }
3568
3569             // assemble String[] { pdb files }, String[] { id for each
3570             // file }, orig_fileloc, SequenceI[][] {{ seqs_file 1 }, {
3571             // seqs_file 2}, boolean[] {
3572             // linkAlignPanel,superposeWithAlignpanel}} from hash
3573             StructureViewerModel jmoldat = structureViewers.get(sviewid);
3574             jmoldat.setAlignWithPanel(jmoldat.isAlignWithPanel()
3575                     | (structureState.hasAlignwithAlignPanel() ? structureState
3576                             .getAlignwithAlignPanel() : false));
3577
3578             /*
3579              * Default colour by linked panel to false if not specified (e.g.
3580              * for pre-2.7 projects)
3581              */
3582             boolean colourWithAlignPanel = jmoldat.isColourWithAlignPanel();
3583             colourWithAlignPanel |= (structureState
3584                     .hasColourwithAlignPanel() ? structureState
3585                     .getColourwithAlignPanel() : false);
3586             jmoldat.setColourWithAlignPanel(colourWithAlignPanel);
3587
3588             /*
3589              * Default colour by viewer to true if not specified (e.g. for
3590              * pre-2.7 projects)
3591              */
3592             boolean colourByViewer = jmoldat.isColourByViewer();
3593             colourByViewer &= structureState.hasColourByJmol() ? structureState
3594                     .getColourByJmol() : true;
3595             jmoldat.setColourByViewer(colourByViewer);
3596
3597             if (jmoldat.getStateData().length() < structureState
3598                     .getContent().length())
3599             {
3600               {
3601                 jmoldat.setStateData(structureState.getContent());
3602               }
3603             }
3604             if (ids[p].getFile() != null)
3605             {
3606               File mapkey = new File(ids[p].getFile());
3607               StructureData seqstrmaps = jmoldat.getFileData().get(mapkey);
3608               if (seqstrmaps == null)
3609               {
3610                 jmoldat.getFileData().put(
3611                         mapkey,
3612                         seqstrmaps = jmoldat.new StructureData(pdbFile,
3613                                 ids[p].getId()));
3614               }
3615               if (!seqstrmaps.getSeqList().contains(seq))
3616               {
3617                 seqstrmaps.getSeqList().add(seq);
3618                 // TODO and chains?
3619               }
3620             }
3621             else
3622             {
3623               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");
3624               warn(errorMessage);
3625             }
3626           }
3627         }
3628       }
3629     }
3630     // Instantiate the associated structure views
3631     for (Entry<String, StructureViewerModel> entry : structureViewers
3632             .entrySet())
3633     {
3634       try
3635       {
3636         createOrLinkStructureViewer(entry, af, ap, jprovider);
3637       } catch (Exception e)
3638       {
3639         System.err.println("Error loading structure viewer: "
3640                 + e.getMessage());
3641         // failed - try the next one
3642       }
3643     }
3644   }
3645
3646   /**
3647    * 
3648    * @param viewerData
3649    * @param af
3650    * @param ap
3651    * @param jprovider
3652    */
3653   protected void createOrLinkStructureViewer(
3654           Entry<String, StructureViewerModel> viewerData, AlignFrame af,
3655           AlignmentPanel ap, jarInputStreamProvider jprovider)
3656   {
3657     final StructureViewerModel stateData = viewerData.getValue();
3658
3659     /*
3660      * Search for any viewer windows already open from other alignment views
3661      * that exactly match the stored structure state
3662      */
3663     StructureViewerBase comp = findMatchingViewer(viewerData);
3664
3665     if (comp != null)
3666     {
3667       linkStructureViewer(ap, comp, stateData);
3668       return;
3669     }
3670
3671     /*
3672      * From 2.9: stateData.type contains JMOL or CHIMERA, data is in jar entry
3673      * "viewer_"+stateData.viewId
3674      */
3675     if (ViewerType.CHIMERA.toString().equals(stateData.getType()))
3676     {
3677       createChimeraViewer(viewerData, af, jprovider);
3678     }
3679     else
3680     {
3681       /*
3682        * else Jmol (if pre-2.9, stateData contains JMOL state string)
3683        */
3684       createJmolViewer(viewerData, af, jprovider);
3685     }
3686   }
3687
3688   /**
3689    * Create a new Chimera viewer.
3690    * 
3691    * @param data
3692    * @param af
3693    * @param jprovider
3694    */
3695   protected void createChimeraViewer(
3696           Entry<String, StructureViewerModel> viewerData, AlignFrame af,
3697           jarInputStreamProvider jprovider)
3698   {
3699     StructureViewerModel data = viewerData.getValue();
3700     String chimeraSessionFile = data.getStateData();
3701
3702     /*
3703      * Copy Chimera session from jar entry "viewer_"+viewId to a temporary file
3704      * 
3705      * NB this is the 'saved' viewId as in the project file XML, _not_ the
3706      * 'uniquified' sviewid used to reconstruct the viewer here
3707      */
3708     String viewerJarEntryName = getViewerJarEntryName(data.getViewId());
3709     chimeraSessionFile = copyJarEntry(jprovider, viewerJarEntryName,
3710             "chimera");
3711
3712     Set<Entry<File, StructureData>> fileData = data.getFileData()
3713             .entrySet();
3714     List<PDBEntry> pdbs = new ArrayList<PDBEntry>();
3715     List<SequenceI[]> allseqs = new ArrayList<SequenceI[]>();
3716     for (Entry<File, StructureData> pdb : fileData)
3717     {
3718       String filePath = pdb.getValue().getFilePath();
3719       String pdbId = pdb.getValue().getPdbId();
3720       // pdbs.add(new PDBEntry(filePath, pdbId));
3721       pdbs.add(new PDBEntry(pdbId, null, PDBEntry.Type.PDB, filePath));
3722       final List<SequenceI> seqList = pdb.getValue().getSeqList();
3723       SequenceI[] seqs = seqList.toArray(new SequenceI[seqList.size()]);
3724       allseqs.add(seqs);
3725     }
3726
3727     boolean colourByChimera = data.isColourByViewer();
3728     boolean colourBySequence = data.isColourWithAlignPanel();
3729
3730     // TODO use StructureViewer as a factory here, see JAL-1761
3731     final PDBEntry[] pdbArray = pdbs.toArray(new PDBEntry[pdbs.size()]);
3732     final SequenceI[][] seqsArray = allseqs.toArray(new SequenceI[allseqs
3733             .size()][]);
3734     String newViewId = viewerData.getKey();
3735
3736     ChimeraViewFrame cvf = new ChimeraViewFrame(chimeraSessionFile,
3737             af.alignPanel, pdbArray, seqsArray, colourByChimera,
3738             colourBySequence, newViewId);
3739     cvf.setSize(data.getWidth(), data.getHeight());
3740     cvf.setLocation(data.getX(), data.getY());
3741   }
3742
3743   /**
3744    * Create a new Jmol window. First parse the Jmol state to translate filenames
3745    * loaded into the view, and record the order in which files are shown in the
3746    * Jmol view, so we can add the sequence mappings in same order.
3747    * 
3748    * @param viewerData
3749    * @param af
3750    * @param jprovider
3751    */
3752   protected void createJmolViewer(
3753           final Entry<String, StructureViewerModel> viewerData,
3754           AlignFrame af, jarInputStreamProvider jprovider)
3755   {
3756     final StructureViewerModel svattrib = viewerData.getValue();
3757     String state = svattrib.getStateData();
3758
3759     /*
3760      * Pre-2.9: state element value is the Jmol state string
3761      * 
3762      * 2.9+: @type is "JMOL", state data is in a Jar file member named "viewer_"
3763      * + viewId
3764      */
3765     if (ViewerType.JMOL.toString().equals(svattrib.getType()))
3766     {
3767       state = readJarEntry(jprovider,
3768               getViewerJarEntryName(svattrib.getViewId()));
3769     }
3770
3771     List<String> pdbfilenames = new ArrayList<String>();
3772     List<SequenceI[]> seqmaps = new ArrayList<SequenceI[]>();
3773     List<String> pdbids = new ArrayList<String>();
3774     StringBuilder newFileLoc = new StringBuilder(64);
3775     int cp = 0, ncp, ecp;
3776     Map<File, StructureData> oldFiles = svattrib.getFileData();
3777     while ((ncp = state.indexOf("load ", cp)) > -1)
3778     {
3779       do
3780       {
3781         // look for next filename in load statement
3782         newFileLoc.append(state.substring(cp,
3783                 ncp = (state.indexOf("\"", ncp + 1) + 1)));
3784         String oldfilenam = state.substring(ncp,
3785                 ecp = state.indexOf("\"", ncp));
3786         // recover the new mapping data for this old filename
3787         // have to normalize filename - since Jmol and jalview do
3788         // filename
3789         // translation differently.
3790         StructureData filedat = oldFiles.get(new File(oldfilenam));
3791         newFileLoc.append(Platform.escapeString(filedat.getFilePath()));
3792         pdbfilenames.add(filedat.getFilePath());
3793         pdbids.add(filedat.getPdbId());
3794         seqmaps.add(filedat.getSeqList().toArray(new SequenceI[0]));
3795         newFileLoc.append("\"");
3796         cp = ecp + 1; // advance beyond last \" and set cursor so we can
3797                       // look for next file statement.
3798       } while ((ncp = state.indexOf("/*file*/", cp)) > -1);
3799     }
3800     if (cp > 0)
3801     {
3802       // just append rest of state
3803       newFileLoc.append(state.substring(cp));
3804     }
3805     else
3806     {
3807       System.err.print("Ignoring incomplete Jmol state for PDB ids: ");
3808       newFileLoc = new StringBuilder(state);
3809       newFileLoc.append("; load append ");
3810       for (File id : oldFiles.keySet())
3811       {
3812         // add this and any other pdb files that should be present in
3813         // the viewer
3814         StructureData filedat = oldFiles.get(id);
3815         newFileLoc.append(filedat.getFilePath());
3816         pdbfilenames.add(filedat.getFilePath());
3817         pdbids.add(filedat.getPdbId());
3818         seqmaps.add(filedat.getSeqList().toArray(new SequenceI[0]));
3819         newFileLoc.append(" \"");
3820         newFileLoc.append(filedat.getFilePath());
3821         newFileLoc.append("\"");
3822
3823       }
3824       newFileLoc.append(";");
3825     }
3826
3827     if (newFileLoc.length() == 0)
3828     {
3829       return;
3830     }
3831     int histbug = newFileLoc.indexOf("history = ");
3832     if (histbug > -1)
3833     {
3834       /*
3835        * change "history = [true|false];" to "history = [1|0];"
3836        */
3837       histbug += 10;
3838       int diff = histbug == -1 ? -1 : newFileLoc.indexOf(";", histbug);
3839       String val = (diff == -1) ? null : newFileLoc
3840               .substring(histbug, diff);
3841       if (val != null && val.length() >= 4)
3842       {
3843         if (val.contains("e")) // eh? what can it be?
3844         {
3845           if (val.trim().equals("true"))
3846           {
3847             val = "1";
3848           }
3849           else
3850           {
3851             val = "0";
3852           }
3853           newFileLoc.replace(histbug, diff, val);
3854         }
3855       }
3856     }
3857
3858     final String[] pdbf = pdbfilenames.toArray(new String[pdbfilenames
3859             .size()]);
3860     final String[] id = pdbids.toArray(new String[pdbids.size()]);
3861     final SequenceI[][] sq = seqmaps
3862             .toArray(new SequenceI[seqmaps.size()][]);
3863     final String fileloc = newFileLoc.toString();
3864     final String sviewid = viewerData.getKey();
3865     final AlignFrame alf = af;
3866     final Rectangle rect = new Rectangle(svattrib.getX(), svattrib.getY(),
3867             svattrib.getWidth(), svattrib.getHeight());
3868     try
3869     {
3870       javax.swing.SwingUtilities.invokeAndWait(new Runnable()
3871       {
3872         @Override
3873         public void run()
3874         {
3875           JalviewStructureDisplayI sview = null;
3876           try
3877           {
3878             sview = new StructureViewer(alf.alignPanel
3879                     .getStructureSelectionManager()).createView(
3880                     StructureViewer.ViewerType.JMOL, pdbf, id, sq,
3881                     alf.alignPanel, svattrib, fileloc, rect, sviewid);
3882             addNewStructureViewer(sview);
3883           } catch (OutOfMemoryError ex)
3884           {
3885             new OOMWarning("restoring structure view for PDB id " + id,
3886                     (OutOfMemoryError) ex.getCause());
3887             if (sview != null && sview.isVisible())
3888             {
3889               sview.closeViewer(false);
3890               sview.setVisible(false);
3891               sview.dispose();
3892             }
3893           }
3894         }
3895       });
3896     } catch (InvocationTargetException ex)
3897     {
3898       warn("Unexpected error when opening Jmol view.", ex);
3899
3900     } catch (InterruptedException e)
3901     {
3902       // e.printStackTrace();
3903     }
3904
3905   }
3906
3907   /**
3908    * Generates a name for the entry in the project jar file to hold state
3909    * information for a structure viewer
3910    * 
3911    * @param viewId
3912    * @return
3913    */
3914   protected String getViewerJarEntryName(String viewId)
3915   {
3916     return VIEWER_PREFIX + viewId;
3917   }
3918
3919   /**
3920    * Returns any open frame that matches given structure viewer data. The match
3921    * is based on the unique viewId, or (for older project versions) the frame's
3922    * geometry.
3923    * 
3924    * @param viewerData
3925    * @return
3926    */
3927   protected StructureViewerBase findMatchingViewer(
3928           Entry<String, StructureViewerModel> viewerData)
3929   {
3930     final String sviewid = viewerData.getKey();
3931     final StructureViewerModel svattrib = viewerData.getValue();
3932     StructureViewerBase comp = null;
3933     JInternalFrame[] frames = getAllFrames();
3934     for (JInternalFrame frame : frames)
3935     {
3936       if (frame instanceof StructureViewerBase)
3937       {
3938         /*
3939          * Post jalview 2.4 schema includes structure view id
3940          */
3941         if (sviewid != null
3942                 && ((StructureViewerBase) frame).getViewId()
3943                         .equals(sviewid))
3944         {
3945           comp = (StructureViewerBase) frame;
3946           break; // break added in 2.9
3947         }
3948         /*
3949          * Otherwise test for matching position and size of viewer frame
3950          */
3951         else if (frame.getX() == svattrib.getX()
3952                 && frame.getY() == svattrib.getY()
3953                 && frame.getHeight() == svattrib.getHeight()
3954                 && frame.getWidth() == svattrib.getWidth())
3955         {
3956           comp = (StructureViewerBase) frame;
3957           // no break in faint hope of an exact match on viewId
3958         }
3959       }
3960     }
3961     return comp;
3962   }
3963
3964   /**
3965    * Link an AlignmentPanel to an existing structure viewer.
3966    * 
3967    * @param ap
3968    * @param viewer
3969    * @param oldFiles
3970    * @param useinViewerSuperpos
3971    * @param usetoColourbyseq
3972    * @param viewerColouring
3973    */
3974   protected void linkStructureViewer(AlignmentPanel ap,
3975           StructureViewerBase viewer, StructureViewerModel stateData)
3976   {
3977     // NOTE: if the jalview project is part of a shared session then
3978     // view synchronization should/could be done here.
3979
3980     final boolean useinViewerSuperpos = stateData.isAlignWithPanel();
3981     final boolean usetoColourbyseq = stateData.isColourWithAlignPanel();
3982     final boolean viewerColouring = stateData.isColourByViewer();
3983     Map<File, StructureData> oldFiles = stateData.getFileData();
3984
3985     /*
3986      * Add mapping for sequences in this view to an already open viewer
3987      */
3988     final AAStructureBindingModel binding = viewer.getBinding();
3989     for (File id : oldFiles.keySet())
3990     {
3991       // add this and any other pdb files that should be present in the
3992       // viewer
3993       StructureData filedat = oldFiles.get(id);
3994       String pdbFile = filedat.getFilePath();
3995       SequenceI[] seq = filedat.getSeqList().toArray(new SequenceI[0]);
3996       binding.getSsm().setMapping(seq, null, pdbFile,
3997               jalview.io.AppletFormatAdapter.FILE);
3998       binding.addSequenceForStructFile(pdbFile, seq);
3999     }
4000     // and add the AlignmentPanel's reference to the view panel
4001     viewer.addAlignmentPanel(ap);
4002     if (useinViewerSuperpos)
4003     {
4004       viewer.useAlignmentPanelForSuperposition(ap);
4005     }
4006     else
4007     {
4008       viewer.excludeAlignmentPanelForSuperposition(ap);
4009     }
4010     if (usetoColourbyseq)
4011     {
4012       viewer.useAlignmentPanelForColourbyseq(ap, !viewerColouring);
4013     }
4014     else
4015     {
4016       viewer.excludeAlignmentPanelForColourbyseq(ap);
4017     }
4018   }
4019
4020   /**
4021    * Get all frames within the Desktop.
4022    * 
4023    * @return
4024    */
4025   protected JInternalFrame[] getAllFrames()
4026   {
4027     JInternalFrame[] frames = null;
4028     // TODO is this necessary - is it safe - risk of hanging?
4029     do
4030     {
4031       try
4032       {
4033         frames = Desktop.desktop.getAllFrames();
4034       } catch (ArrayIndexOutOfBoundsException e)
4035       {
4036         // occasional No such child exceptions are thrown here...
4037         try
4038         {
4039           Thread.sleep(10);
4040         } catch (InterruptedException f)
4041         {
4042         }
4043       }
4044     } while (frames == null);
4045     return frames;
4046   }
4047
4048   /**
4049    * 
4050    * @param supported
4051    *          - minimum version we are comparing against
4052    * @param version
4053    *          - version of data being processsed.
4054    * @return true if version is development/null or evaluates to the same or
4055    *         later X.Y.Z (where X,Y,Z are like [0-9]+b?[0-9]*)
4056    */
4057   public static boolean isVersionStringLaterThan(String supported,
4058           String version)
4059   {
4060     if (version == null || version.equalsIgnoreCase("DEVELOPMENT BUILD")
4061             || version.equalsIgnoreCase("Test")
4062             || version.equalsIgnoreCase("AUTOMATED BUILD"))
4063     {
4064       System.err.println("Assuming project file with "
4065               + (version == null ? "null" : version)
4066               + " is compatible with Jalview version " + supported);
4067       return true;
4068     }
4069     else
4070     {
4071       StringTokenizer currentV = new StringTokenizer(supported, "."), fileV = new StringTokenizer(
4072               version, ".");
4073       while (currentV.hasMoreTokens() && fileV.hasMoreTokens())
4074       {
4075         // convert b to decimal to catch bugfix releases within a series
4076         String curT = currentV.nextToken().toLowerCase().replace('b', '.');
4077         String fileT = fileV.nextToken().toLowerCase().replace('b', '.');
4078         try
4079         {
4080           float supportedVersionToken = Float.parseFloat(curT);
4081           float myVersiontoken = Float.parseFloat(fileT);
4082           if (supportedVersionToken > myVersiontoken)
4083           {
4084             // current version is newer than the version that wrote the file
4085             return false;
4086           }
4087           if (supportedVersionToken < myVersiontoken)
4088           {
4089             // current version is older than the version that wrote the file
4090             return true;
4091           }
4092         } catch (NumberFormatException nfe)
4093         {
4094           System.err
4095                   .println("** WARNING: Version comparison failed for tokens ("
4096                           + curT
4097                           + ") and ("
4098                           + fileT
4099                           + ")\n** Current: '"
4100                           + supported + "' and Version: '" + version + "'");
4101         }
4102       }
4103       if (currentV.hasMoreElements())
4104       {
4105         // fileV has no minor version but identical series to current
4106         return false;
4107       }
4108     }
4109     return true;
4110   }
4111
4112   Vector<JalviewStructureDisplayI> newStructureViewers = null;
4113
4114   protected void addNewStructureViewer(JalviewStructureDisplayI sview)
4115   {
4116     if (newStructureViewers != null)
4117     {
4118       sview.getBinding().setFinishedLoadingFromArchive(false);
4119       newStructureViewers.add(sview);
4120     }
4121   }
4122
4123   protected void setLoadingFinishedForNewStructureViewers()
4124   {
4125     if (newStructureViewers != null)
4126     {
4127       for (JalviewStructureDisplayI sview : newStructureViewers)
4128       {
4129         sview.getBinding().setFinishedLoadingFromArchive(true);
4130       }
4131       newStructureViewers.clear();
4132       newStructureViewers = null;
4133     }
4134   }
4135
4136   AlignFrame loadViewport(String file, JSeq[] JSEQ,
4137           List<SequenceI> hiddenSeqs, Alignment al,
4138           JalviewModelSequence jms, Viewport view, String uniqueSeqSetId,
4139           String viewId, List<JvAnnotRow> autoAlan)
4140   {
4141     AlignFrame af = null;
4142     af = new AlignFrame(al, view.getWidth(), view.getHeight(),
4143             uniqueSeqSetId, viewId);
4144
4145     af.setFileName(file, "Jalview");
4146
4147     for (int i = 0; i < JSEQ.length; i++)
4148     {
4149       af.viewport.setSequenceColour(af.viewport.getAlignment()
4150               .getSequenceAt(i), new java.awt.Color(JSEQ[i].getColour()));
4151     }
4152
4153     af.viewport.setGatherViewsHere(view.getGatheredViews());
4154
4155     if (view.getSequenceSetId() != null)
4156     {
4157       AlignmentViewport av = viewportsAdded.get(uniqueSeqSetId);
4158
4159       af.viewport.setSequenceSetId(uniqueSeqSetId);
4160       if (av != null)
4161       {
4162         // propagate shared settings to this new view
4163         af.viewport.setHistoryList(av.getHistoryList());
4164         af.viewport.setRedoList(av.getRedoList());
4165       }
4166       else
4167       {
4168         viewportsAdded.put(uniqueSeqSetId, af.viewport);
4169       }
4170       // TODO: check if this method can be called repeatedly without
4171       // side-effects if alignpanel already registered.
4172       PaintRefresher.Register(af.alignPanel, uniqueSeqSetId);
4173     }
4174     // apply Hidden regions to view.
4175     if (hiddenSeqs != null)
4176     {
4177       for (int s = 0; s < JSEQ.length; s++)
4178       {
4179         jalview.datamodel.SequenceGroup hidden = new jalview.datamodel.SequenceGroup();
4180
4181         for (int r = 0; r < JSEQ[s].getHiddenSequencesCount(); r++)
4182         {
4183           hidden.addSequence(
4184                   al.getSequenceAt(JSEQ[s].getHiddenSequences(r)), false);
4185         }
4186         af.viewport.hideRepSequences(al.getSequenceAt(s), hidden);
4187       }
4188
4189       // jalview.datamodel.SequenceI[] hseqs = new
4190       // jalview.datamodel.SequenceI[hiddenSeqs
4191       // .size()];
4192       //
4193       // for (int s = 0; s < hiddenSeqs.size(); s++)
4194       // {
4195       // hseqs[s] = (jalview.datamodel.SequenceI) hiddenSeqs.elementAt(s);
4196       // }
4197
4198       SequenceI[] hseqs = hiddenSeqs.toArray(new SequenceI[hiddenSeqs
4199               .size()]);
4200       af.viewport.hideSequence(hseqs);
4201
4202     }
4203     // recover view properties and display parameters
4204     if (view.getViewName() != null)
4205     {
4206       af.viewport.viewName = view.getViewName();
4207       af.setInitialTabVisible();
4208     }
4209     af.setBounds(view.getXpos(), view.getYpos(), view.getWidth(),
4210             view.getHeight());
4211
4212     af.viewport.setShowAnnotation(view.getShowAnnotation());
4213     af.viewport.setAbovePIDThreshold(view.getPidSelected());
4214
4215     af.viewport.setColourText(view.getShowColourText());
4216
4217     af.viewport.setConservationSelected(view.getConservationSelected());
4218     af.viewport.setShowJVSuffix(view.getShowFullId());
4219     af.viewport.setRightAlignIds(view.getRightAlignIds());
4220     af.viewport.setFont(
4221             new java.awt.Font(view.getFontName(), view.getFontStyle(), view
4222                     .getFontSize()), true);
4223     ViewStyleI vs = af.viewport.getViewStyle();
4224     vs.setScaleProteinAsCdna(view.isScaleProteinAsCdna());
4225     af.viewport.setViewStyle(vs);
4226     // TODO: allow custom charWidth/Heights to be restored by updating them
4227     // after setting font - which means set above to false
4228     af.viewport.setRenderGaps(view.getRenderGaps());
4229     af.viewport.setWrapAlignment(view.getWrapAlignment());
4230     af.viewport.setShowAnnotation(view.getShowAnnotation());
4231
4232     af.viewport.setShowBoxes(view.getShowBoxes());
4233
4234     af.viewport.setShowText(view.getShowText());
4235
4236     af.viewport.setTextColour(new java.awt.Color(view.getTextCol1()));
4237     af.viewport.setTextColour2(new java.awt.Color(view.getTextCol2()));
4238     af.viewport.setThresholdTextColour(view.getTextColThreshold());
4239     af.viewport.setShowUnconserved(view.hasShowUnconserved() ? view
4240             .isShowUnconserved() : false);
4241     af.viewport.setStartRes(view.getStartRes());
4242     af.viewport.setStartSeq(view.getStartSeq());
4243     af.alignPanel.updateLayout();
4244     ColourSchemeI cs = null;
4245     // apply colourschemes
4246     if (view.getBgColour() != null)
4247     {
4248       if (view.getBgColour().startsWith("ucs"))
4249       {
4250         cs = getUserColourScheme(jms, view.getBgColour());
4251       }
4252       else if (view.getBgColour().startsWith("Annotation"))
4253       {
4254         AnnotationColours viewAnnColour = view.getAnnotationColours();
4255         cs = constructAnnotationColour(viewAnnColour, af, al, jms, true);
4256
4257         // annpos
4258
4259       }
4260       else
4261       {
4262         cs = ColourSchemeProperty.getColour(al, view.getBgColour());
4263       }
4264
4265       if (cs != null)
4266       {
4267         cs.setThreshold(view.getPidThreshold(), true);
4268         cs.setConsensus(af.viewport.getSequenceConsensusHash());
4269       }
4270     }
4271
4272     af.viewport.setGlobalColourScheme(cs);
4273     af.viewport.setColourAppliesToAllGroups(false);
4274
4275     if (view.getConservationSelected() && cs != null)
4276     {
4277       cs.setConservationInc(view.getConsThreshold());
4278     }
4279
4280     af.changeColour(cs);
4281
4282     af.viewport.setColourAppliesToAllGroups(true);
4283
4284     af.viewport.setShowSequenceFeatures(view.getShowSequenceFeatures());
4285
4286     if (view.hasCentreColumnLabels())
4287     {
4288       af.viewport.setCentreColumnLabels(view.getCentreColumnLabels());
4289     }
4290     if (view.hasIgnoreGapsinConsensus())
4291     {
4292       af.viewport.setIgnoreGapsConsensus(view.getIgnoreGapsinConsensus(),
4293               null);
4294     }
4295     if (view.hasFollowHighlight())
4296     {
4297       af.viewport.setFollowHighlight(view.getFollowHighlight());
4298     }
4299     if (view.hasFollowSelection())
4300     {
4301       af.viewport.followSelection = view.getFollowSelection();
4302     }
4303     if (view.hasShowConsensusHistogram())
4304     {
4305       af.viewport.setShowConsensusHistogram(view
4306               .getShowConsensusHistogram());
4307     }
4308     else
4309     {
4310       af.viewport.setShowConsensusHistogram(true);
4311     }
4312     if (view.hasShowSequenceLogo())
4313     {
4314       af.viewport.setShowSequenceLogo(view.getShowSequenceLogo());
4315     }
4316     else
4317     {
4318       af.viewport.setShowSequenceLogo(false);
4319     }
4320     if (view.hasNormaliseSequenceLogo())
4321     {
4322       af.viewport.setNormaliseSequenceLogo(view.getNormaliseSequenceLogo());
4323     }
4324     if (view.hasShowDbRefTooltip())
4325     {
4326       af.viewport.setShowDBRefs(view.getShowDbRefTooltip());
4327     }
4328     if (view.hasShowNPfeatureTooltip())
4329     {
4330       af.viewport.setShowNPFeats(view.hasShowNPfeatureTooltip());
4331     }
4332     if (view.hasShowGroupConsensus())
4333     {
4334       af.viewport.setShowGroupConsensus(view.getShowGroupConsensus());
4335     }
4336     else
4337     {
4338       af.viewport.setShowGroupConsensus(false);
4339     }
4340     if (view.hasShowGroupConservation())
4341     {
4342       af.viewport.setShowGroupConservation(view.getShowGroupConservation());
4343     }
4344     else
4345     {
4346       af.viewport.setShowGroupConservation(false);
4347     }
4348
4349     // recover featre settings
4350     if (jms.getFeatureSettings() != null)
4351     {
4352       FeaturesDisplayed fdi;
4353       af.viewport.setFeaturesDisplayed(fdi = new FeaturesDisplayed());
4354       String[] renderOrder = new String[jms.getFeatureSettings()
4355               .getSettingCount()];
4356       Hashtable featureGroups = new Hashtable();
4357       Hashtable featureColours = new Hashtable();
4358       Hashtable featureOrder = new Hashtable();
4359
4360       for (int fs = 0; fs < jms.getFeatureSettings().getSettingCount(); fs++)
4361       {
4362         Setting setting = jms.getFeatureSettings().getSetting(fs);
4363         if (setting.hasMincolour())
4364         {
4365           GraduatedColor gc = setting.hasMin() ? new GraduatedColor(
4366                   new java.awt.Color(setting.getMincolour()),
4367                   new java.awt.Color(setting.getColour()),
4368                   setting.getMin(), setting.getMax()) : new GraduatedColor(
4369                   new java.awt.Color(setting.getMincolour()),
4370                   new java.awt.Color(setting.getColour()), 0, 1);
4371           if (setting.hasThreshold())
4372           {
4373             gc.setThresh(setting.getThreshold());
4374             gc.setThreshType(setting.getThreshstate());
4375           }
4376           gc.setAutoScaled(true); // default
4377           if (setting.hasAutoScale())
4378           {
4379             gc.setAutoScaled(setting.getAutoScale());
4380           }
4381           if (setting.hasColourByLabel())
4382           {
4383             gc.setColourByLabel(setting.getColourByLabel());
4384           }
4385           // and put in the feature colour table.
4386           featureColours.put(setting.getType(), gc);
4387         }
4388         else
4389         {
4390           featureColours.put(setting.getType(),
4391                   new java.awt.Color(setting.getColour()));
4392         }
4393         renderOrder[fs] = setting.getType();
4394         if (setting.hasOrder())
4395         {
4396           featureOrder.put(setting.getType(), setting.getOrder());
4397         }
4398         else
4399         {
4400           featureOrder.put(setting.getType(), new Float(fs
4401                   / jms.getFeatureSettings().getSettingCount()));
4402         }
4403         if (setting.getDisplay())
4404         {
4405           fdi.setVisible(setting.getType());
4406         }
4407       }
4408       Hashtable fgtable = new Hashtable();
4409       for (int gs = 0; gs < jms.getFeatureSettings().getGroupCount(); gs++)
4410       {
4411         Group grp = jms.getFeatureSettings().getGroup(gs);
4412         fgtable.put(grp.getName(), new Boolean(grp.getDisplay()));
4413       }
4414       // FeatureRendererSettings frs = new FeatureRendererSettings(renderOrder,
4415       // fgtable, featureColours, jms.getFeatureSettings().hasTransparency() ?
4416       // jms.getFeatureSettings().getTransparency() : 0.0, featureOrder);
4417       FeatureRendererSettings frs = new FeatureRendererSettings(
4418               renderOrder, fgtable, featureColours, 1.0f, featureOrder);
4419       af.alignPanel.getSeqPanel().seqCanvas.getFeatureRenderer()
4420               .transferSettings(frs);
4421
4422     }
4423
4424     if (view.getHiddenColumnsCount() > 0)
4425     {
4426       for (int c = 0; c < view.getHiddenColumnsCount(); c++)
4427       {
4428         af.viewport.hideColumns(view.getHiddenColumns(c).getStart(), view
4429                 .getHiddenColumns(c).getEnd() // +1
4430                 );
4431       }
4432     }
4433     if (view.getCalcIdParam() != null)
4434     {
4435       for (CalcIdParam calcIdParam : view.getCalcIdParam())
4436       {
4437         if (calcIdParam != null)
4438         {
4439           if (recoverCalcIdParam(calcIdParam, af.viewport))
4440           {
4441           }
4442           else
4443           {
4444             warn("Couldn't recover parameters for "
4445                     + calcIdParam.getCalcId());
4446           }
4447         }
4448       }
4449     }
4450     af.setMenusFromViewport(af.viewport);
4451
4452     // TODO: we don't need to do this if the viewport is aready visible.
4453     /*
4454      * Add the AlignFrame to the desktop (it may be 'gathered' later), unless it
4455      * has a 'cdna/protein complement' view, in which case save it in order to
4456      * populate a SplitFrame once all views have been read in.
4457      */
4458     String complementaryViewId = view.getComplementId();
4459     if (complementaryViewId == null)
4460     {
4461       Desktop.addInternalFrame(af, view.getTitle(), view.getWidth(),
4462               view.getHeight());
4463       // recompute any autoannotation
4464       af.alignPanel.updateAnnotation(false, true);
4465       reorderAutoannotation(af, al, autoAlan);
4466       af.alignPanel.alignmentChanged();
4467     }
4468     else
4469     {
4470       splitFrameCandidates.put(view, af);
4471     }
4472     return af;
4473   }
4474
4475   private ColourSchemeI constructAnnotationColour(
4476           AnnotationColours viewAnnColour, AlignFrame af, Alignment al,
4477           JalviewModelSequence jms, boolean checkGroupAnnColour)
4478   {
4479     boolean propagateAnnColour = false;
4480     ColourSchemeI cs = null;
4481     AlignmentI annAlignment = af != null ? af.viewport.getAlignment() : al;
4482     if (checkGroupAnnColour && al.getGroups() != null
4483             && al.getGroups().size() > 0)
4484     {
4485       // pre 2.8.1 behaviour
4486       // check to see if we should transfer annotation colours
4487       propagateAnnColour = true;
4488       for (jalview.datamodel.SequenceGroup sg : al.getGroups())
4489       {
4490         if (sg.cs instanceof AnnotationColourGradient)
4491         {
4492           propagateAnnColour = false;
4493         }
4494       }
4495     }
4496     // int find annotation
4497     if (annAlignment.getAlignmentAnnotation() != null)
4498     {
4499       for (int i = 0; i < annAlignment.getAlignmentAnnotation().length; i++)
4500       {
4501         if (annAlignment.getAlignmentAnnotation()[i].label
4502                 .equals(viewAnnColour.getAnnotation()))
4503         {
4504           if (annAlignment.getAlignmentAnnotation()[i].getThreshold() == null)
4505           {
4506             annAlignment.getAlignmentAnnotation()[i]
4507                     .setThreshold(new jalview.datamodel.GraphLine(
4508                             viewAnnColour.getThreshold(), "Threshold",
4509                             java.awt.Color.black)
4510
4511                     );
4512           }
4513
4514           if (viewAnnColour.getColourScheme().equals("None"))
4515           {
4516             cs = new AnnotationColourGradient(
4517                     annAlignment.getAlignmentAnnotation()[i],
4518                     new java.awt.Color(viewAnnColour.getMinColour()),
4519                     new java.awt.Color(viewAnnColour.getMaxColour()),
4520                     viewAnnColour.getAboveThreshold());
4521           }
4522           else if (viewAnnColour.getColourScheme().startsWith("ucs"))
4523           {
4524             cs = new AnnotationColourGradient(
4525                     annAlignment.getAlignmentAnnotation()[i],
4526                     getUserColourScheme(jms,
4527                             viewAnnColour.getColourScheme()),
4528                     viewAnnColour.getAboveThreshold());
4529           }
4530           else
4531           {
4532             cs = new AnnotationColourGradient(
4533                     annAlignment.getAlignmentAnnotation()[i],
4534                     ColourSchemeProperty.getColour(al,
4535                             viewAnnColour.getColourScheme()),
4536                     viewAnnColour.getAboveThreshold());
4537           }
4538           if (viewAnnColour.hasPerSequence())
4539           {
4540             ((AnnotationColourGradient) cs).setSeqAssociated(viewAnnColour
4541                     .isPerSequence());
4542           }
4543           if (viewAnnColour.hasPredefinedColours())
4544           {
4545             ((AnnotationColourGradient) cs)
4546                     .setPredefinedColours(viewAnnColour
4547                             .isPredefinedColours());
4548           }
4549           if (propagateAnnColour && al.getGroups() != null)
4550           {
4551             // Also use these settings for all the groups
4552             for (int g = 0; g < al.getGroups().size(); g++)
4553             {
4554               jalview.datamodel.SequenceGroup sg = al.getGroups().get(g);
4555
4556               if (sg.cs == null)
4557               {
4558                 continue;
4559               }
4560
4561               /*
4562                * if (viewAnnColour.getColourScheme().equals("None" )) { sg.cs =
4563                * new AnnotationColourGradient(
4564                * annAlignment.getAlignmentAnnotation()[i], new
4565                * java.awt.Color(viewAnnColour. getMinColour()), new
4566                * java.awt.Color(viewAnnColour. getMaxColour()),
4567                * viewAnnColour.getAboveThreshold()); } else
4568                */
4569               {
4570                 sg.cs = new AnnotationColourGradient(
4571                         annAlignment.getAlignmentAnnotation()[i], sg.cs,
4572                         viewAnnColour.getAboveThreshold());
4573                 if (cs instanceof AnnotationColourGradient)
4574                 {
4575                   if (viewAnnColour.hasPerSequence())
4576                   {
4577                     ((AnnotationColourGradient) cs)
4578                             .setSeqAssociated(viewAnnColour.isPerSequence());
4579                   }
4580                   if (viewAnnColour.hasPredefinedColours())
4581                   {
4582                     ((AnnotationColourGradient) cs)
4583                             .setPredefinedColours(viewAnnColour
4584                                     .isPredefinedColours());
4585                   }
4586                 }
4587               }
4588
4589             }
4590           }
4591
4592           break;
4593         }
4594
4595       }
4596     }
4597     return cs;
4598   }
4599
4600   private void reorderAutoannotation(AlignFrame af, Alignment al,
4601           List<JvAnnotRow> autoAlan)
4602   {
4603     // copy over visualization settings for autocalculated annotation in the
4604     // view
4605     if (al.getAlignmentAnnotation() != null)
4606     {
4607       /**
4608        * Kludge for magic autoannotation names (see JAL-811)
4609        */
4610       String[] magicNames = new String[] { "Consensus", "Quality",
4611           "Conservation" };
4612       JvAnnotRow nullAnnot = new JvAnnotRow(-1, null);
4613       Hashtable<String, JvAnnotRow> visan = new Hashtable<String, JvAnnotRow>();
4614       for (String nm : magicNames)
4615       {
4616         visan.put(nm, nullAnnot);
4617       }
4618       for (JvAnnotRow auan : autoAlan)
4619       {
4620         visan.put(auan.template.label
4621                 + (auan.template.getCalcId() == null ? "" : "\t"
4622                         + auan.template.getCalcId()), auan);
4623       }
4624       int hSize = al.getAlignmentAnnotation().length;
4625       List<JvAnnotRow> reorder = new ArrayList<JvAnnotRow>();
4626       // work through any autoCalculated annotation already on the view
4627       // removing it if it should be placed in a different location on the
4628       // annotation panel.
4629       List<String> remains = new ArrayList<String>(visan.keySet());
4630       for (int h = 0; h < hSize; h++)
4631       {
4632         jalview.datamodel.AlignmentAnnotation jalan = al
4633                 .getAlignmentAnnotation()[h];
4634         if (jalan.autoCalculated)
4635         {
4636           String k;
4637           JvAnnotRow valan = visan.get(k = jalan.label);
4638           if (jalan.getCalcId() != null)
4639           {
4640             valan = visan.get(k = jalan.label + "\t" + jalan.getCalcId());
4641           }
4642
4643           if (valan != null)
4644           {
4645             // delete the auto calculated row from the alignment
4646             al.deleteAnnotation(jalan, false);
4647             remains.remove(k);
4648             hSize--;
4649             h--;
4650             if (valan != nullAnnot)
4651             {
4652               if (jalan != valan.template)
4653               {
4654                 // newly created autoannotation row instance
4655                 // so keep a reference to the visible annotation row
4656                 // and copy over all relevant attributes
4657                 if (valan.template.graphHeight >= 0)
4658
4659                 {
4660                   jalan.graphHeight = valan.template.graphHeight;
4661                 }
4662                 jalan.visible = valan.template.visible;
4663               }
4664               reorder.add(new JvAnnotRow(valan.order, jalan));
4665             }
4666           }
4667         }
4668       }
4669       // Add any (possibly stale) autocalculated rows that were not appended to
4670       // the view during construction
4671       for (String other : remains)
4672       {
4673         JvAnnotRow othera = visan.get(other);
4674         if (othera != nullAnnot && othera.template.getCalcId() != null
4675                 && othera.template.getCalcId().length() > 0)
4676         {
4677           reorder.add(othera);
4678         }
4679       }
4680       // now put the automatic annotation in its correct place
4681       int s = 0, srt[] = new int[reorder.size()];
4682       JvAnnotRow[] rws = new JvAnnotRow[reorder.size()];
4683       for (JvAnnotRow jvar : reorder)
4684       {
4685         rws[s] = jvar;
4686         srt[s++] = jvar.order;
4687       }
4688       reorder.clear();
4689       jalview.util.QuickSort.sort(srt, rws);
4690       // and re-insert the annotation at its correct position
4691       for (JvAnnotRow jvar : rws)
4692       {
4693         al.addAnnotation(jvar.template, jvar.order);
4694       }
4695       af.alignPanel.adjustAnnotationHeight();
4696     }
4697   }
4698
4699   Hashtable skipList = null;
4700
4701   /**
4702    * TODO remove this method
4703    * 
4704    * @param view
4705    * @return AlignFrame bound to sequenceSetId from view, if one exists. private
4706    *         AlignFrame getSkippedFrame(Viewport view) { if (skipList==null) {
4707    *         throw new Error("Implementation Error. No skipList defined for this
4708    *         Jalview2XML instance."); } return (AlignFrame)
4709    *         skipList.get(view.getSequenceSetId()); }
4710    */
4711
4712   /**
4713    * Check if the Jalview view contained in object should be skipped or not.
4714    * 
4715    * @param object
4716    * @return true if view's sequenceSetId is a key in skipList
4717    */
4718   private boolean skipViewport(JalviewModel object)
4719   {
4720     if (skipList == null)
4721     {
4722       return false;
4723     }
4724     String id;
4725     if (skipList.containsKey(id = object.getJalviewModelSequence()
4726             .getViewport()[0].getSequenceSetId()))
4727     {
4728       if (Cache.log != null && Cache.log.isDebugEnabled())
4729       {
4730         Cache.log.debug("Skipping seuqence set id " + id);
4731       }
4732       return true;
4733     }
4734     return false;
4735   }
4736
4737   public void addToSkipList(AlignFrame af)
4738   {
4739     if (skipList == null)
4740     {
4741       skipList = new Hashtable();
4742     }
4743     skipList.put(af.getViewport().getSequenceSetId(), af);
4744   }
4745
4746   public void clearSkipList()
4747   {
4748     if (skipList != null)
4749     {
4750       skipList.clear();
4751       skipList = null;
4752     }
4753   }
4754
4755   private void recoverDatasetFor(SequenceSet vamsasSet, Alignment al,
4756           boolean ignoreUnrefed)
4757   {
4758     jalview.datamodel.Alignment ds = getDatasetFor(vamsasSet.getDatasetId());
4759     Vector dseqs = null;
4760     if (ds == null)
4761     {
4762       // create a list of new dataset sequences
4763       dseqs = new Vector();
4764     }
4765     for (int i = 0, iSize = vamsasSet.getSequenceCount(); i < iSize; i++)
4766     {
4767       Sequence vamsasSeq = vamsasSet.getSequence(i);
4768       ensureJalviewDatasetSequence(vamsasSeq, ds, dseqs, ignoreUnrefed);
4769     }
4770     // create a new dataset
4771     if (ds == null)
4772     {
4773       SequenceI[] dsseqs = new SequenceI[dseqs.size()];
4774       dseqs.copyInto(dsseqs);
4775       ds = new jalview.datamodel.Alignment(dsseqs);
4776       debug("Created new dataset " + vamsasSet.getDatasetId()
4777               + " for alignment " + System.identityHashCode(al));
4778       addDatasetRef(vamsasSet.getDatasetId(), ds);
4779     }
4780     // set the dataset for the newly imported alignment.
4781     if (al.getDataset() == null && !ignoreUnrefed)
4782     {
4783       al.setDataset(ds);
4784     }
4785   }
4786
4787   /**
4788    * 
4789    * @param vamsasSeq
4790    *          sequence definition to create/merge dataset sequence for
4791    * @param ds
4792    *          dataset alignment
4793    * @param dseqs
4794    *          vector to add new dataset sequence to
4795    */
4796   private void ensureJalviewDatasetSequence(Sequence vamsasSeq,
4797           AlignmentI ds, Vector dseqs, boolean ignoreUnrefed)
4798   {
4799     // JBP TODO: Check this is called for AlCodonFrames to support recovery of
4800     // xRef Codon Maps
4801     SequenceI sq = seqRefIds.get(vamsasSeq.getId());
4802     SequenceI dsq = null;
4803     if (sq != null && sq.getDatasetSequence() != null)
4804     {
4805       dsq = sq.getDatasetSequence();
4806     }
4807     if (sq == null && ignoreUnrefed)
4808     {
4809       return;
4810     }
4811     String sqid = vamsasSeq.getDsseqid();
4812     if (dsq == null)
4813     {
4814       // need to create or add a new dataset sequence reference to this sequence
4815       if (sqid != null)
4816       {
4817         dsq = seqRefIds.get(sqid);
4818       }
4819       // check again
4820       if (dsq == null)
4821       {
4822         // make a new dataset sequence
4823         dsq = sq.createDatasetSequence();
4824         if (sqid == null)
4825         {
4826           // make up a new dataset reference for this sequence
4827           sqid = seqHash(dsq);
4828         }
4829         dsq.setVamsasId(uniqueSetSuffix + sqid);
4830         seqRefIds.put(sqid, dsq);
4831         if (ds == null)
4832         {
4833           if (dseqs != null)
4834           {
4835             dseqs.addElement(dsq);
4836           }
4837         }
4838         else
4839         {
4840           ds.addSequence(dsq);
4841         }
4842       }
4843       else
4844       {
4845         if (sq != dsq)
4846         { // make this dataset sequence sq's dataset sequence
4847           sq.setDatasetSequence(dsq);
4848           // and update the current dataset alignment
4849           if (ds == null)
4850           {
4851             if (dseqs != null)
4852             {
4853               if (!dseqs.contains(dsq))
4854               {
4855                 dseqs.add(dsq);
4856               }
4857             }
4858             else
4859             {
4860               if (ds.findIndex(dsq) < 0)
4861               {
4862                 ds.addSequence(dsq);
4863               }
4864             }
4865           }
4866         }
4867       }
4868     }
4869     // TODO: refactor this as a merge dataset sequence function
4870     // now check that sq (the dataset sequence) sequence really is the union of
4871     // all references to it
4872     // boolean pre = sq.getStart() < dsq.getStart();
4873     // boolean post = sq.getEnd() > dsq.getEnd();
4874     // if (pre || post)
4875     if (sq != dsq)
4876     {
4877       // StringBuffer sb = new StringBuffer();
4878       String newres = jalview.analysis.AlignSeq.extractGaps(
4879               jalview.util.Comparison.GapChars, sq.getSequenceAsString());
4880       if (!newres.equalsIgnoreCase(dsq.getSequenceAsString())
4881               && newres.length() > dsq.getLength())
4882       {
4883         // Update with the longer sequence.
4884         synchronized (dsq)
4885         {
4886           /*
4887            * if (pre) { sb.insert(0, newres .substring(0, dsq.getStart() -
4888            * sq.getStart())); dsq.setStart(sq.getStart()); } if (post) {
4889            * sb.append(newres.substring(newres.length() - sq.getEnd() -
4890            * dsq.getEnd())); dsq.setEnd(sq.getEnd()); }
4891            */
4892           dsq.setSequence(newres);
4893         }
4894         // TODO: merges will never happen if we 'know' we have the real dataset
4895         // sequence - this should be detected when id==dssid
4896         System.err
4897                 .println("DEBUG Notice:  Merged dataset sequence (if you see this often, post at http://issues.jalview.org/browse/JAL-1474)"); // ("
4898         // + (pre ? "prepended" : "") + " "
4899         // + (post ? "appended" : ""));
4900       }
4901     }
4902   }
4903
4904   /*
4905    * TODO use AlignmentI here and in related methods - needs
4906    * AlignmentI.getDataset() changed to return AlignmentI instead of Alignment
4907    */
4908   Hashtable<String, Alignment> datasetIds = null;
4909
4910   IdentityHashMap<Alignment, String> dataset2Ids = null;
4911
4912   private Alignment getDatasetFor(String datasetId)
4913   {
4914     if (datasetIds == null)
4915     {
4916       datasetIds = new Hashtable<String, Alignment>();
4917       return null;
4918     }
4919     if (datasetIds.containsKey(datasetId))
4920     {
4921       return datasetIds.get(datasetId);
4922     }
4923     return null;
4924   }
4925
4926   private void addDatasetRef(String datasetId, Alignment dataset)
4927   {
4928     if (datasetIds == null)
4929     {
4930       datasetIds = new Hashtable<String, Alignment>();
4931     }
4932     datasetIds.put(datasetId, dataset);
4933   }
4934
4935   /**
4936    * make a new dataset ID for this jalview dataset alignment
4937    * 
4938    * @param dataset
4939    * @return
4940    */
4941   private String getDatasetIdRef(Alignment dataset)
4942   {
4943     if (dataset.getDataset() != null)
4944     {
4945       warn("Serious issue!  Dataset Object passed to getDatasetIdRef is not a Jalview DATASET alignment...");
4946     }
4947     String datasetId = makeHashCode(dataset, null);
4948     if (datasetId == null)
4949     {
4950       // make a new datasetId and record it
4951       if (dataset2Ids == null)
4952       {
4953         dataset2Ids = new IdentityHashMap<Alignment, String>();
4954       }
4955       else
4956       {
4957         datasetId = dataset2Ids.get(dataset);
4958       }
4959       if (datasetId == null)
4960       {
4961         datasetId = "ds" + dataset2Ids.size() + 1;
4962         dataset2Ids.put(dataset, datasetId);
4963       }
4964     }
4965     return datasetId;
4966   }
4967
4968   private void addDBRefs(SequenceI datasetSequence, Sequence sequence)
4969   {
4970     for (int d = 0; d < sequence.getDBRefCount(); d++)
4971     {
4972       DBRef dr = sequence.getDBRef(d);
4973       jalview.datamodel.DBRefEntry entry = new jalview.datamodel.DBRefEntry(
4974               sequence.getDBRef(d).getSource(), sequence.getDBRef(d)
4975                       .getVersion(), sequence.getDBRef(d).getAccessionId());
4976       if (dr.getMapping() != null)
4977       {
4978         entry.setMap(addMapping(dr.getMapping()));
4979       }
4980       datasetSequence.addDBRef(entry);
4981     }
4982   }
4983
4984   private jalview.datamodel.Mapping addMapping(Mapping m)
4985   {
4986     SequenceI dsto = null;
4987     // Mapping m = dr.getMapping();
4988     int fr[] = new int[m.getMapListFromCount() * 2];
4989     Enumeration f = m.enumerateMapListFrom();
4990     for (int _i = 0; f.hasMoreElements(); _i += 2)
4991     {
4992       MapListFrom mf = (MapListFrom) f.nextElement();
4993       fr[_i] = mf.getStart();
4994       fr[_i + 1] = mf.getEnd();
4995     }
4996     int fto[] = new int[m.getMapListToCount() * 2];
4997     f = m.enumerateMapListTo();
4998     for (int _i = 0; f.hasMoreElements(); _i += 2)
4999     {
5000       MapListTo mf = (MapListTo) f.nextElement();
5001       fto[_i] = mf.getStart();
5002       fto[_i + 1] = mf.getEnd();
5003     }
5004     jalview.datamodel.Mapping jmap = new jalview.datamodel.Mapping(dsto,
5005             fr, fto, (int) m.getMapFromUnit(), (int) m.getMapToUnit());
5006     if (m.getMappingChoice() != null)
5007     {
5008       MappingChoice mc = m.getMappingChoice();
5009       if (mc.getDseqFor() != null)
5010       {
5011         String dsfor = "" + mc.getDseqFor();
5012         if (seqRefIds.containsKey(dsfor))
5013         {
5014           /**
5015            * recover from hash
5016            */
5017           jmap.setTo(seqRefIds.get(dsfor));
5018         }
5019         else
5020         {
5021           frefedSequence.add(new Object[] { dsfor, jmap });
5022         }
5023       }
5024       else
5025       {
5026         /**
5027          * local sequence definition
5028          */
5029         Sequence ms = mc.getSequence();
5030         SequenceI djs = null;
5031         String sqid = ms.getDsseqid();
5032         if (sqid != null && sqid.length() > 0)
5033         {
5034           /*
5035            * recover dataset sequence
5036            */
5037           djs = seqRefIds.get(sqid);
5038         }
5039         else
5040         {
5041           System.err
5042                   .println("Warning - making up dataset sequence id for DbRef sequence map reference");
5043           sqid = ((Object) ms).toString(); // make up a new hascode for
5044           // undefined dataset sequence hash
5045           // (unlikely to happen)
5046         }
5047
5048         if (djs == null)
5049         {
5050           /**
5051            * make a new dataset sequence and add it to refIds hash
5052            */
5053           djs = new jalview.datamodel.Sequence(ms.getName(),
5054                   ms.getSequence());
5055           djs.setStart(jmap.getMap().getToLowest());
5056           djs.setEnd(jmap.getMap().getToHighest());
5057           djs.setVamsasId(uniqueSetSuffix + sqid);
5058           jmap.setTo(djs);
5059           seqRefIds.put(sqid, djs);
5060
5061         }
5062         jalview.bin.Cache.log.debug("about to recurse on addDBRefs.");
5063         addDBRefs(djs, ms);
5064
5065       }
5066     }
5067     return (jmap);
5068
5069   }
5070
5071   public jalview.gui.AlignmentPanel copyAlignPanel(AlignmentPanel ap,
5072           boolean keepSeqRefs)
5073   {
5074     initSeqRefs();
5075     JalviewModel jm = saveState(ap, null, null, null);
5076
5077     if (!keepSeqRefs)
5078     {
5079       clearSeqRefs();
5080       jm.getJalviewModelSequence().getViewport(0).setSequenceSetId(null);
5081     }
5082     else
5083     {
5084       uniqueSetSuffix = "";
5085       jm.getJalviewModelSequence().getViewport(0).setId(null); // we don't
5086       // overwrite the
5087       // view we just
5088       // copied
5089     }
5090     if (this.frefedSequence == null)
5091     {
5092       frefedSequence = new Vector<Object[]>();
5093     }
5094
5095     viewportsAdded.clear();
5096
5097     AlignFrame af = loadFromObject(jm, null, false, null);
5098     af.alignPanels.clear();
5099     af.closeMenuItem_actionPerformed(true);
5100
5101     /*
5102      * if(ap.av.getAlignment().getAlignmentAnnotation()!=null) { for(int i=0;
5103      * i<ap.av.getAlignment().getAlignmentAnnotation().length; i++) {
5104      * if(!ap.av.getAlignment().getAlignmentAnnotation()[i].autoCalculated) {
5105      * af.alignPanel.av.getAlignment().getAlignmentAnnotation()[i] =
5106      * ap.av.getAlignment().getAlignmentAnnotation()[i]; } } }
5107      */
5108
5109     return af.alignPanel;
5110   }
5111
5112   /**
5113    * flag indicating if hashtables should be cleared on finalization TODO this
5114    * flag may not be necessary
5115    */
5116   private final boolean _cleartables = true;
5117
5118   private Hashtable jvids2vobj;
5119
5120   /*
5121    * (non-Javadoc)
5122    * 
5123    * @see java.lang.Object#finalize()
5124    */
5125   @Override
5126   protected void finalize() throws Throwable
5127   {
5128     // really make sure we have no buried refs left.
5129     if (_cleartables)
5130     {
5131       clearSeqRefs();
5132     }
5133     this.seqRefIds = null;
5134     this.seqsToIds = null;
5135     super.finalize();
5136   }
5137
5138   private void warn(String msg)
5139   {
5140     warn(msg, null);
5141   }
5142
5143   private void warn(String msg, Exception e)
5144   {
5145     if (Cache.log != null)
5146     {
5147       if (e != null)
5148       {
5149         Cache.log.warn(msg, e);
5150       }
5151       else
5152       {
5153         Cache.log.warn(msg);
5154       }
5155     }
5156     else
5157     {
5158       System.err.println("Warning: " + msg);
5159       if (e != null)
5160       {
5161         e.printStackTrace();
5162       }
5163     }
5164   }
5165
5166   private void debug(String string)
5167   {
5168     debug(string, null);
5169   }
5170
5171   private void debug(String msg, Exception e)
5172   {
5173     if (Cache.log != null)
5174     {
5175       if (e != null)
5176       {
5177         Cache.log.debug(msg, e);
5178       }
5179       else
5180       {
5181         Cache.log.debug(msg);
5182       }
5183     }
5184     else
5185     {
5186       System.err.println("Warning: " + msg);
5187       if (e != null)
5188       {
5189         e.printStackTrace();
5190       }
5191     }
5192   }
5193
5194   /**
5195    * set the object to ID mapping tables used to write/recover objects and XML
5196    * ID strings for the jalview project. If external tables are provided then
5197    * finalize and clearSeqRefs will not clear the tables when the Jalview2XML
5198    * object goes out of scope. - also populates the datasetIds hashtable with
5199    * alignment objects containing dataset sequences
5200    * 
5201    * @param vobj2jv
5202    *          Map from ID strings to jalview datamodel
5203    * @param jv2vobj
5204    *          Map from jalview datamodel to ID strings
5205    * 
5206    * 
5207    */
5208   public void setObjectMappingTables(Hashtable vobj2jv,
5209           IdentityHashMap jv2vobj)
5210   {
5211     this.jv2vobj = jv2vobj;
5212     this.vobj2jv = vobj2jv;
5213     Iterator ds = jv2vobj.keySet().iterator();
5214     String id;
5215     while (ds.hasNext())
5216     {
5217       Object jvobj = ds.next();
5218       id = jv2vobj.get(jvobj).toString();
5219       if (jvobj instanceof jalview.datamodel.Alignment)
5220       {
5221         if (((jalview.datamodel.Alignment) jvobj).getDataset() == null)
5222         {
5223           addDatasetRef(id, (jalview.datamodel.Alignment) jvobj);
5224         }
5225       }
5226       else if (jvobj instanceof jalview.datamodel.Sequence)
5227       {
5228         // register sequence object so the XML parser can recover it.
5229         if (seqRefIds == null)
5230         {
5231           seqRefIds = new HashMap<String, SequenceI>();
5232         }
5233         if (seqsToIds == null)
5234         {
5235           seqsToIds = new IdentityHashMap<SequenceI, String>();
5236         }
5237         seqRefIds.put(jv2vobj.get(jvobj).toString(), (SequenceI) jvobj);
5238         seqsToIds.put((SequenceI) jvobj, id);
5239       }
5240       else if (jvobj instanceof jalview.datamodel.AlignmentAnnotation)
5241       {
5242         String anid;
5243         AlignmentAnnotation jvann = (AlignmentAnnotation) jvobj;
5244         annotationIds.put(anid = jv2vobj.get(jvobj).toString(), jvann);
5245         if (jvann.annotationId == null)
5246         {
5247           jvann.annotationId = anid;
5248         }
5249         if (!jvann.annotationId.equals(anid))
5250         {
5251           // TODO verify that this is the correct behaviour
5252           this.warn("Overriding Annotation ID for " + anid
5253                   + " from different id : " + jvann.annotationId);
5254           jvann.annotationId = anid;
5255         }
5256       }
5257       else if (jvobj instanceof String)
5258       {
5259         if (jvids2vobj == null)
5260         {
5261           jvids2vobj = new Hashtable();
5262           jvids2vobj.put(jvobj, jv2vobj.get(jvobj).toString());
5263         }
5264       }
5265       else
5266       {
5267         Cache.log.debug("Ignoring " + jvobj.getClass() + " (ID = " + id);
5268       }
5269     }
5270   }
5271
5272   /**
5273    * set the uniqueSetSuffix used to prefix/suffix object IDs for jalview
5274    * objects created from the project archive. If string is null (default for
5275    * construction) then suffix will be set automatically.
5276    * 
5277    * @param string
5278    */
5279   public void setUniqueSetSuffix(String string)
5280   {
5281     uniqueSetSuffix = string;
5282
5283   }
5284
5285   /**
5286    * uses skipList2 as the skipList for skipping views on sequence sets
5287    * associated with keys in the skipList
5288    * 
5289    * @param skipList2
5290    */
5291   public void setSkipList(Hashtable skipList2)
5292   {
5293     skipList = skipList2;
5294   }
5295
5296   /**
5297    * Reads the jar entry of given name and returns its contents, or null if the
5298    * entry is not found.
5299    * 
5300    * @param jprovider
5301    * @param jarEntryName
5302    * @return
5303    */
5304   protected String readJarEntry(jarInputStreamProvider jprovider,
5305           String jarEntryName)
5306   {
5307     String result = null;
5308     BufferedReader in = null;
5309
5310     try
5311     {
5312       /*
5313        * Reopen the jar input stream and traverse its entries to find a matching
5314        * name
5315        */
5316       JarInputStream jin = jprovider.getJarInputStream();
5317       JarEntry entry = null;
5318       do
5319       {
5320         entry = jin.getNextJarEntry();
5321       } while (entry != null && !entry.getName().equals(jarEntryName));
5322
5323       if (entry != null)
5324       {
5325         StringBuilder out = new StringBuilder(256);
5326         in = new BufferedReader(new InputStreamReader(jin, UTF_8));
5327         String data;
5328
5329         while ((data = in.readLine()) != null)
5330         {
5331           out.append(data);
5332         }
5333         result = out.toString();
5334       }
5335       else
5336       {
5337         warn("Couldn't find entry in Jalview Jar for " + jarEntryName);
5338       }
5339     } catch (Exception ex)
5340     {
5341       ex.printStackTrace();
5342     } finally
5343     {
5344       if (in != null)
5345       {
5346         try
5347         {
5348           in.close();
5349         } catch (IOException e)
5350         {
5351           // ignore
5352         }
5353       }
5354     }
5355
5356     return result;
5357   }
5358
5359   /**
5360    * Returns an incrementing counter (0, 1, 2...)
5361    * 
5362    * @return
5363    */
5364   private synchronized int nextCounter()
5365   {
5366     return counter++;
5367   }
5368 }