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