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