Merge branch 'improvement/JAL-4409_implement_extra_schemes_in_getdown' into develop
[jalview.git] / src / jalview / project / 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.project;
22
23 import static jalview.math.RotatableMatrix.Axis.X;
24 import static jalview.math.RotatableMatrix.Axis.Y;
25 import static jalview.math.RotatableMatrix.Axis.Z;
26
27 import java.awt.Color;
28 import java.awt.Font;
29 import java.awt.Rectangle;
30 import java.io.BufferedReader;
31 import java.io.ByteArrayInputStream;
32 import java.io.File;
33 import java.io.FileInputStream;
34 import java.io.FileOutputStream;
35 import java.io.IOException;
36 import java.io.InputStream;
37 import java.io.InputStreamReader;
38 import java.io.OutputStream;
39 import java.io.OutputStreamWriter;
40 import java.io.PrintWriter;
41 import java.lang.reflect.InvocationTargetException;
42 import java.math.BigInteger;
43 import java.net.MalformedURLException;
44 import java.net.URL;
45 import java.util.ArrayList;
46 import java.util.Arrays;
47 import java.util.BitSet;
48 import java.util.Collections;
49 import java.util.Enumeration;
50 import java.util.GregorianCalendar;
51 import java.util.HashMap;
52 import java.util.HashSet;
53 import java.util.Hashtable;
54 import java.util.IdentityHashMap;
55 import java.util.Iterator;
56 import java.util.LinkedHashMap;
57 import java.util.List;
58 import java.util.Locale;
59 import java.util.Map;
60 import java.util.Map.Entry;
61 import java.util.Set;
62 import java.util.Vector;
63 import java.util.jar.JarEntry;
64 import java.util.jar.JarInputStream;
65 import java.util.jar.JarOutputStream;
66
67 import javax.swing.JInternalFrame;
68 import javax.swing.SwingUtilities;
69 import javax.xml.bind.JAXBContext;
70 import javax.xml.bind.JAXBElement;
71 import javax.xml.bind.Marshaller;
72 import javax.xml.datatype.DatatypeConfigurationException;
73 import javax.xml.datatype.DatatypeFactory;
74 import javax.xml.datatype.XMLGregorianCalendar;
75 import javax.xml.stream.XMLInputFactory;
76 import javax.xml.stream.XMLStreamReader;
77
78 import jalview.analysis.Conservation;
79 import jalview.analysis.PCA;
80 import jalview.analysis.PaSiMap;
81 import jalview.analysis.scoremodels.ScoreModels;
82 import jalview.analysis.scoremodels.SimilarityParams;
83 import jalview.api.FeatureColourI;
84 import jalview.api.ViewStyleI;
85 import jalview.api.analysis.ScoreModelI;
86 import jalview.api.analysis.SimilarityParamsI;
87 import jalview.api.structures.JalviewStructureDisplayI;
88 import jalview.bin.Cache;
89 import jalview.bin.Console;
90 import jalview.bin.Jalview;
91 import jalview.datamodel.AlignedCodonFrame;
92 import jalview.datamodel.Alignment;
93 import jalview.datamodel.AlignmentAnnotation;
94 import jalview.datamodel.AlignmentI;
95 import jalview.datamodel.ContactMatrix;
96 import jalview.datamodel.ContactMatrixI;
97 import jalview.datamodel.DBRefEntry;
98 import jalview.datamodel.FloatContactMatrix;
99 import jalview.datamodel.GeneLocus;
100 import jalview.datamodel.GraphLine;
101 import jalview.datamodel.GroupSet;
102 import jalview.datamodel.PDBEntry;
103 import jalview.datamodel.Point;
104 import jalview.datamodel.RnaViewerModel;
105 import jalview.datamodel.SequenceFeature;
106 import jalview.datamodel.SequenceGroup;
107 import jalview.datamodel.SequenceI;
108 import jalview.datamodel.StructureViewerModel;
109 import jalview.datamodel.StructureViewerModel.StructureData;
110 import jalview.datamodel.features.FeatureMatcher;
111 import jalview.datamodel.features.FeatureMatcherI;
112 import jalview.datamodel.features.FeatureMatcherSet;
113 import jalview.datamodel.features.FeatureMatcherSetI;
114 import jalview.ext.varna.RnaModel;
115 import jalview.gui.AlignFrame;
116 import jalview.gui.AlignViewport;
117 import jalview.gui.AlignmentPanel;
118 import jalview.gui.AppVarna;
119 import jalview.gui.Desktop;
120 import jalview.gui.JvOptionPane;
121 import jalview.gui.OOMWarning;
122 import jalview.gui.OverviewPanel;
123 import jalview.gui.PCAPanel;
124 import jalview.gui.PaSiMapPanel;
125 import jalview.gui.PaintRefresher;
126 import jalview.gui.SplitFrame;
127 import jalview.gui.StructureViewer;
128 import jalview.gui.StructureViewer.ViewerType;
129 import jalview.gui.StructureViewerBase;
130 import jalview.gui.TreePanel;
131 import jalview.io.BackupFiles;
132 import jalview.io.DataSourceType;
133 import jalview.io.FileFormat;
134 import jalview.io.NewickFile;
135 import jalview.math.Matrix;
136 import jalview.math.MatrixI;
137 import jalview.renderer.ResidueShaderI;
138 import jalview.schemes.AnnotationColourGradient;
139 import jalview.schemes.ColourSchemeI;
140 import jalview.schemes.ColourSchemeProperty;
141 import jalview.schemes.FeatureColour;
142 import jalview.schemes.ResidueProperties;
143 import jalview.schemes.UserColourScheme;
144 import jalview.structure.StructureSelectionManager;
145 import jalview.structures.models.AAStructureBindingModel;
146 import jalview.util.Format;
147 import jalview.util.HttpUtils;
148 import jalview.util.MessageManager;
149 import jalview.util.Platform;
150 import jalview.util.StringUtils;
151 import jalview.util.jarInputStreamProvider;
152 import jalview.util.matcher.Condition;
153 import jalview.viewmodel.AlignmentViewport;
154 import jalview.viewmodel.PCAModel;
155 import jalview.viewmodel.PaSiMapModel;
156 import jalview.viewmodel.ViewportRanges;
157 import jalview.viewmodel.seqfeatures.FeatureRendererModel;
158 import jalview.viewmodel.seqfeatures.FeatureRendererSettings;
159 import jalview.viewmodel.seqfeatures.FeaturesDisplayed;
160 import jalview.ws.datamodel.MappableContactMatrixI;
161 import jalview.ws.datamodel.alphafold.PAEContactMatrix;
162 import jalview.ws.jws2.Jws2Discoverer;
163 import jalview.ws.jws2.dm.AAConSettings;
164 import jalview.ws.jws2.jabaws2.Jws2Instance;
165 import jalview.ws.params.ArgumentI;
166 import jalview.ws.params.AutoCalcSetting;
167 import jalview.ws.params.WsParamSetI;
168 import jalview.xml.binding.jalview.AlcodonFrame;
169 import jalview.xml.binding.jalview.AlcodonFrame.AlcodMap;
170 import jalview.xml.binding.jalview.Annotation;
171 import jalview.xml.binding.jalview.Annotation.ThresholdLine;
172 import jalview.xml.binding.jalview.AnnotationColourScheme;
173 import jalview.xml.binding.jalview.AnnotationElement;
174 import jalview.xml.binding.jalview.DoubleMatrix;
175 import jalview.xml.binding.jalview.DoubleVector;
176 import jalview.xml.binding.jalview.Feature;
177 import jalview.xml.binding.jalview.Feature.OtherData;
178 import jalview.xml.binding.jalview.FeatureMatcherSet.CompoundMatcher;
179 import jalview.xml.binding.jalview.FilterBy;
180 import jalview.xml.binding.jalview.JalviewModel;
181 import jalview.xml.binding.jalview.JalviewModel.FeatureSettings;
182 import jalview.xml.binding.jalview.JalviewModel.FeatureSettings.Group;
183 import jalview.xml.binding.jalview.JalviewModel.FeatureSettings.Setting;
184 import jalview.xml.binding.jalview.JalviewModel.JGroup;
185 import jalview.xml.binding.jalview.JalviewModel.JSeq;
186 import jalview.xml.binding.jalview.JalviewModel.JSeq.Pdbids;
187 import jalview.xml.binding.jalview.JalviewModel.JSeq.Pdbids.StructureState;
188 import jalview.xml.binding.jalview.JalviewModel.JSeq.RnaViewer;
189 import jalview.xml.binding.jalview.JalviewModel.JSeq.RnaViewer.SecondaryStructure;
190 import jalview.xml.binding.jalview.JalviewModel.PcaViewer;
191 import jalview.xml.binding.jalview.JalviewModel.PcaViewer.Axis;
192 import jalview.xml.binding.jalview.JalviewModel.PcaViewer.SeqPointMax;
193 import jalview.xml.binding.jalview.JalviewModel.PcaViewer.SeqPointMin;
194 import jalview.xml.binding.jalview.JalviewModel.PcaViewer.SequencePoint;
195 import jalview.xml.binding.jalview.JalviewModel.Tree;
196 import jalview.xml.binding.jalview.JalviewModel.UserColours;
197 import jalview.xml.binding.jalview.JalviewModel.Viewport;
198 import jalview.xml.binding.jalview.JalviewModel.Viewport.CalcIdParam;
199 import jalview.xml.binding.jalview.JalviewModel.Viewport.HiddenColumns;
200 import jalview.xml.binding.jalview.JalviewModel.Viewport.Overview;
201 import jalview.xml.binding.jalview.JalviewUserColours;
202 import jalview.xml.binding.jalview.JalviewUserColours.Colour;
203 import jalview.xml.binding.jalview.MapListType;
204 import jalview.xml.binding.jalview.MapListType.MapListFrom;
205 import jalview.xml.binding.jalview.MapListType.MapListTo;
206 import jalview.xml.binding.jalview.MapOnAMatrixType;
207 import jalview.xml.binding.jalview.Mapping;
208 import jalview.xml.binding.jalview.MatrixType;
209 import jalview.xml.binding.jalview.NoValueColour;
210 import jalview.xml.binding.jalview.ObjectFactory;
211 import jalview.xml.binding.jalview.PcaDataType;
212 import jalview.xml.binding.jalview.Pdbentry.Property;
213 import jalview.xml.binding.jalview.Sequence;
214 import jalview.xml.binding.jalview.Sequence.DBRef;
215 import jalview.xml.binding.jalview.SequenceSet;
216 import jalview.xml.binding.jalview.SequenceSet.SequenceSetProperties;
217 import jalview.xml.binding.jalview.ThresholdType;
218 import jalview.xml.binding.jalview.VAMSAS;
219
220 /**
221  * Write out the current jalview desktop state as a Jalview XML stream.
222  * 
223  * Note: the vamsas objects referred to here are primitive versions of the
224  * VAMSAS project schema elements - they are not the same and most likely never
225  * will be :)
226  * 
227  * @author $author$
228  * @version $Revision: 1.134 $
229  */
230 public class Jalview2XML
231 {
232
233   // BH 2018 we add the .jvp binary extension to J2S so that
234   // it will declare that binary when we do the file save from the browser
235
236   static
237   {
238     Platform.addJ2SBinaryType(".jvp?");
239   }
240
241   private static final String VIEWER_PREFIX = "viewer_";
242
243   private static final String RNA_PREFIX = "rna_";
244
245   private static final String UTF_8 = "UTF-8";
246
247   /**
248    * used in decision if quit confirmation should be issued
249    */
250   private static boolean stateSavedUpToDate = false;
251
252   /**
253    * prefix for recovering datasets for alignments with multiple views where
254    * non-existent dataset IDs were written for some views
255    */
256   private static final String UNIQSEQSETID = "uniqueSeqSetId.";
257
258   // use this with nextCounter() to make unique names for entities
259   private int counter = 0;
260
261   /*
262    * SequenceI reference -> XML ID string in jalview XML. Populated as XML reps
263    * of sequence objects are created.
264    */
265   IdentityHashMap<SequenceI, String> seqsToIds = null;
266
267   /**
268    * jalview XML Sequence ID to jalview sequence object reference (both dataset
269    * and alignment sequences. Populated as XML reps of sequence objects are
270    * created.)
271    */
272   Map<String, SequenceI> seqRefIds = null;
273
274   Map<String, SequenceI> incompleteSeqs = null;
275
276   List<forwardRef> frefedSequence = null;
277
278   boolean raiseGUI = true; // whether errors are raised in dialog boxes or not
279
280   /*
281    * Map of reconstructed AlignFrame objects that appear to have come from
282    * SplitFrame objects (have a dna/protein complement view).
283    */
284   private Map<Viewport, AlignFrame> splitFrameCandidates = new HashMap<>();
285
286   /*
287    * Map from displayed rna structure models to their saved session state jar
288    * entry names
289    */
290   private Map<RnaModel, String> rnaSessions = new HashMap<>();
291
292   /**
293    * map from contact matrices to their XML ids
294    */
295   private Map<ContactMatrixI, String> contactMatrices = new HashMap<>();
296
297   private Map<String, ContactMatrixI> contactMatrixRefs = new HashMap<>();
298
299   private List<jalview.xml.binding.jalview.MatrixType> xmlMatrices = new ArrayList<>();
300
301   /**
302    * A helper method for safely using the value of an optional attribute that
303    * may be null if not present in the XML. Answers the boolean value, or false
304    * if null.
305    * 
306    * @param b
307    * @return
308    */
309   public static boolean safeBoolean(Boolean b)
310   {
311     return b == null ? false : b.booleanValue();
312   }
313
314   /**
315    * A helper method for safely using the value of an optional attribute that
316    * may be null if not present in the XML. Answers the integer value, or zero
317    * if null.
318    * 
319    * @param i
320    * @return
321    */
322   public static int safeInt(Integer i)
323   {
324     return i == null ? 0 : i.intValue();
325   }
326
327   /**
328    * A helper method for safely using the value of an optional attribute that
329    * may be null if not present in the XML. Answers the float value, or zero if
330    * null.
331    * 
332    * @param f
333    * @return
334    */
335   public static float safeFloat(Float f)
336   {
337     return f == null ? 0f : f.floatValue();
338   }
339
340   /**
341    * create/return unique hash string for sq
342    * 
343    * @param sq
344    * @return new or existing unique string for sq
345    */
346   String seqHash(SequenceI sq)
347   {
348     if (seqsToIds == null)
349     {
350       initSeqRefs();
351     }
352     if (seqsToIds.containsKey(sq))
353     {
354       return seqsToIds.get(sq);
355     }
356     else
357     {
358       // create sequential key
359       String key = "sq" + (seqsToIds.size() + 1);
360       key = makeHashCode(sq, key); // check we don't have an external reference
361       // for it already.
362       seqsToIds.put(sq, key);
363       return key;
364     }
365   }
366
367   void initSeqRefs()
368   {
369     if (seqsToIds == null)
370     {
371       seqsToIds = new IdentityHashMap<>();
372     }
373     if (seqRefIds == null)
374     {
375       seqRefIds = new HashMap<>();
376     }
377     if (incompleteSeqs == null)
378     {
379       incompleteSeqs = new HashMap<>();
380     }
381     if (frefedSequence == null)
382     {
383       frefedSequence = new ArrayList<>();
384     }
385   }
386
387   public Jalview2XML()
388   {
389   }
390
391   public Jalview2XML(boolean raiseGUI)
392   {
393     this.raiseGUI = raiseGUI;
394   }
395
396   /**
397    * base class for resolving forward references to an as-yet unmarshalled
398    * object referenced by already unmarshalled objects
399    * 
400    * @author jprocter
401    *
402    */
403   abstract class forwardRef
404   {
405     String sref;
406
407     String type;
408
409     public forwardRef(String _sref, String type)
410     {
411       sref = _sref;
412       this.type = type;
413     }
414
415     public String getSref()
416     {
417       return sref;
418     }
419
420     public abstract boolean isResolvable();
421
422     /**
423      * @return true if the forward reference was fully resolved
424      */
425     abstract boolean resolve();
426
427     @Override
428     public String toString()
429     {
430       return type + " reference to " + sref;
431     }
432   }
433
434   /**
435    * resolve forward references to sequences by their ID
436    * 
437    * @author jprocter
438    */
439   abstract class SeqFref extends forwardRef
440   {
441     public SeqFref(String _sref, String type)
442     {
443       super(_sref, type);
444     }
445
446     public SequenceI getSrefSeq()
447     {
448       return seqRefIds.get(sref);
449     }
450
451     public boolean isResolvable()
452     {
453       return seqRefIds.get(sref) != null;
454     }
455
456     public SequenceI getSrefDatasetSeq()
457     {
458       SequenceI sq = seqRefIds.get(sref);
459       if (sq != null)
460       {
461         while (sq.getDatasetSequence() != null)
462         {
463           sq = sq.getDatasetSequence();
464         }
465       }
466       return sq;
467     }
468   }
469
470   /**
471    * create forward reference for a mapping
472    * 
473    * @param sref
474    * @param _jmap
475    * @return
476    */
477   public SeqFref newMappingRef(final String sref,
478           final jalview.datamodel.Mapping _jmap)
479   {
480     SeqFref fref = new SeqFref(sref, "Mapping")
481     {
482       public jalview.datamodel.Mapping jmap = _jmap;
483
484       @Override
485       boolean resolve()
486       {
487         SequenceI seq = getSrefDatasetSeq();
488         if (seq == null)
489         {
490           return false;
491         }
492         jmap.setTo(seq);
493         return true;
494       }
495     };
496     return fref;
497   }
498
499   public SeqFref newAlcodMapRef(final String sref,
500           final AlignedCodonFrame _cf,
501           final jalview.datamodel.Mapping _jmap)
502   {
503
504     SeqFref fref = new SeqFref(sref, "Codon Frame")
505     {
506       AlignedCodonFrame cf = _cf;
507
508       public jalview.datamodel.Mapping mp = _jmap;
509
510       @Override
511       public boolean isResolvable()
512       {
513         return super.isResolvable() && mp.getTo() != null;
514       }
515
516       @Override
517       boolean resolve()
518       {
519         SequenceI seq = getSrefDatasetSeq();
520         if (seq == null)
521         {
522           return false;
523         }
524         cf.addMap(seq, mp.getTo(), mp.getMap());
525         return true;
526       }
527     };
528     return fref;
529   }
530
531   public forwardRef newMatrixFref(final String matRef,
532           final jalview.util.MapList mapping, final AlignmentAnnotation jaa)
533   {
534     forwardRef fref = new forwardRef(matRef,
535             "Matrix Reference for sequence and annotation")
536     {
537
538       @Override
539       boolean resolve()
540       {
541         ContactMatrixI cm = contactMatrixRefs.get(matRef);
542         PAEContactMatrix newpae = new PAEContactMatrix(jaa.sequenceRef,
543                 mapping, cm);
544
545         jaa.sequenceRef.addContactListFor(jaa, newpae);
546         return true;
547       }
548
549       @Override
550       public boolean isResolvable()
551       {
552         return (contactMatrixRefs.get(matRef) != null);
553       }
554     };
555     return fref;
556   }
557
558   public void resolveFrefedSequences()
559   {
560     Iterator<forwardRef> nextFref = frefedSequence.iterator();
561     int toresolve = frefedSequence.size();
562     int unresolved = 0, failedtoresolve = 0;
563     while (nextFref.hasNext())
564     {
565       forwardRef ref = nextFref.next();
566       if (ref.isResolvable())
567       {
568         try
569         {
570           if (ref.resolve())
571           {
572             nextFref.remove();
573           }
574           else
575           {
576             failedtoresolve++;
577           }
578         } catch (Exception x)
579         {
580           jalview.bin.Console.errPrintln(
581                   "IMPLEMENTATION ERROR: Failed to resolve forward reference for sequence "
582                           + ref.getSref());
583           x.printStackTrace();
584           failedtoresolve++;
585         }
586       }
587       else
588       {
589         unresolved++;
590       }
591     }
592     if (unresolved > 0)
593     {
594       jalview.bin.Console.errPrintln("Jalview Project Import: There were "
595               + unresolved
596               + " forward references left unresolved on the stack.");
597     }
598     if (failedtoresolve > 0)
599     {
600       jalview.bin.Console.errPrintln("SERIOUS! " + failedtoresolve
601               + " resolvable forward references failed to resolve.");
602     }
603     if (incompleteSeqs != null && incompleteSeqs.size() > 0)
604     {
605       jalview.bin.Console.errPrintln(
606               "Jalview Project Import: There are " + incompleteSeqs.size()
607                       + " sequences which may have incomplete metadata.");
608       if (incompleteSeqs.size() < 10)
609       {
610         for (SequenceI s : incompleteSeqs.values())
611         {
612           jalview.bin.Console.errPrintln(s.toString());
613         }
614       }
615       else
616       {
617         jalview.bin.Console.errPrintln(
618                 "Too many to report. Skipping output of incomplete sequences.");
619       }
620     }
621   }
622
623   /**
624    * This maintains a map of viewports, the key being the seqSetId. Important to
625    * set historyItem and redoList for multiple views
626    */
627   Map<String, AlignViewport> viewportsAdded = new HashMap<>();
628
629   Map<String, AlignmentAnnotation> annotationIds = new HashMap<>();
630
631   String uniqueSetSuffix = "";
632
633   /**
634    * List of pdbfiles added to Jar
635    */
636   List<String> pdbfiles = null;
637
638   // SAVES SEVERAL ALIGNMENT WINDOWS TO SAME JARFILE
639   public void saveState(File statefile)
640   {
641     FileOutputStream fos = null;
642
643     try
644     {
645
646       fos = new FileOutputStream(statefile);
647
648       JarOutputStream jout = new JarOutputStream(fos);
649       saveState(jout);
650       fos.close();
651
652     } catch (Exception e)
653     {
654       Console.error("Couln't write Jalview state to " + statefile, e);
655       // TODO: inform user of the problem - they need to know if their data was
656       // not saved !
657       if (errorMessage == null)
658       {
659         errorMessage = "Did't write Jalview Archive to output file '"
660                 + statefile + "' - See console error log for details";
661       }
662       else
663       {
664         errorMessage += "(Didn't write Jalview Archive to output file '"
665                 + statefile + ")";
666       }
667       e.printStackTrace();
668     } finally
669     {
670       if (fos != null)
671       {
672         try
673         {
674           fos.close();
675         } catch (IOException e)
676         {
677           // ignore
678         }
679       }
680     }
681     reportErrors();
682   }
683
684   /**
685    * Writes a jalview project archive to the given Jar output stream.
686    * 
687    * @param jout
688    */
689   public void saveState(JarOutputStream jout)
690   {
691     AlignFrame[] frames = Desktop.getDesktopAlignFrames();
692
693     setStateSavedUpToDate(true);
694
695     if (Cache.getDefault("DEBUG_DELAY_SAVE", false))
696     {
697       int n = debugDelaySave;
698       int i = 0;
699       while (i < n)
700       {
701         Console.debug("***** debugging save sleep " + i + "/" + n);
702         try
703         {
704           Thread.sleep(1000);
705         } catch (InterruptedException e)
706         {
707           // TODO Auto-generated catch block
708           e.printStackTrace();
709         }
710         i++;
711       }
712     }
713
714     if (frames == null)
715     {
716       return;
717     }
718     saveAllFrames(Arrays.asList(frames), jout);
719   }
720
721   /**
722    * core method for storing state for a set of AlignFrames.
723    * 
724    * @param frames
725    *          - frames involving all data to be exported (including containing
726    *          splitframes)
727    * @param jout
728    *          - project output stream
729    */
730   private void saveAllFrames(List<AlignFrame> frames, JarOutputStream jout)
731   {
732     Hashtable<String, AlignFrame> dsses = new Hashtable<>();
733
734     /*
735      * ensure cached data is clear before starting
736      */
737     // todo tidy up seqRefIds, seqsToIds initialisation / reset
738     rnaSessions.clear();
739     splitFrameCandidates.clear();
740
741     try
742     {
743
744       // NOTE UTF-8 MUST BE USED FOR WRITING UNICODE CHARS
745       // //////////////////////////////////////////////////
746
747       List<String> shortNames = new ArrayList<>();
748       List<String> viewIds = new ArrayList<>();
749
750       // REVERSE ORDER
751       for (int i = frames.size() - 1; i > -1; i--)
752       {
753         AlignFrame af = frames.get(i);
754         // skip ?
755         if (skipList != null && skipList
756                 .containsKey(af.getViewport().getSequenceSetId()))
757         {
758           continue;
759         }
760
761         String shortName = makeFilename(af, shortNames);
762
763         int apSize = af.getAlignPanels().size();
764
765         for (int ap = 0; ap < apSize; ap++)
766         {
767           AlignmentPanel apanel = (AlignmentPanel) af.getAlignPanels()
768                   .get(ap);
769           String fileName = apSize == 1 ? shortName : ap + shortName;
770           if (!fileName.endsWith(".xml"))
771           {
772             fileName = fileName + ".xml";
773           }
774
775           saveState(apanel, fileName, jout, viewIds);
776
777           String dssid = getDatasetIdRef(
778                   af.getViewport().getAlignment().getDataset());
779           if (!dsses.containsKey(dssid))
780           {
781             dsses.put(dssid, af);
782           }
783         }
784       }
785
786       writeDatasetFor(dsses, "" + jout.hashCode() + " " + uniqueSetSuffix,
787               jout);
788
789       try
790       {
791         jout.flush();
792       } catch (Exception foo)
793       {
794       }
795       jout.close();
796     } catch (Exception ex)
797     {
798       // TODO: inform user of the problem - they need to know if their data was
799       // not saved !
800       if (errorMessage == null)
801       {
802         errorMessage = "Couldn't write Jalview Archive - see error output for details";
803       }
804       ex.printStackTrace();
805     }
806   }
807
808   /**
809    * Generates a distinct file name, based on the title of the AlignFrame, by
810    * appending _n for increasing n until an unused name is generated. The new
811    * name (without its extension) is added to the list.
812    * 
813    * @param af
814    * @param namesUsed
815    * @return the generated name, with .xml extension
816    */
817   protected String makeFilename(AlignFrame af, List<String> namesUsed)
818   {
819     String shortName = af.getTitle();
820
821     if (shortName.indexOf(File.separatorChar) > -1)
822     {
823       shortName = shortName
824               .substring(shortName.lastIndexOf(File.separatorChar) + 1);
825     }
826
827     int count = 1;
828
829     while (namesUsed.contains(shortName))
830     {
831       if (shortName.endsWith("_" + (count - 1)))
832       {
833         shortName = shortName.substring(0, shortName.lastIndexOf("_"));
834       }
835
836       shortName = shortName.concat("_" + count);
837       count++;
838     }
839
840     namesUsed.add(shortName);
841
842     if (!shortName.endsWith(".xml"))
843     {
844       shortName = shortName + ".xml";
845     }
846     return shortName;
847   }
848
849   // USE THIS METHOD TO SAVE A SINGLE ALIGNMENT WINDOW
850   public boolean saveAlignment(AlignFrame af, String jarFile,
851           String fileName)
852   {
853     try
854     {
855       // create backupfiles object and get new temp filename destination
856       boolean doBackup = BackupFiles.getEnabled();
857       BackupFiles backupfiles = doBackup ? new BackupFiles(jarFile) : null;
858       FileOutputStream fos = new FileOutputStream(
859               doBackup ? backupfiles.getTempFilePath() : jarFile);
860
861       if (Cache.getDefault("DEBUG_DELAY_SAVE", false))
862       {
863         int n = debugDelaySave;
864         int i = 0;
865         while (i < n)
866         {
867           Console.debug("***** debugging save sleep " + i + "/" + n);
868           try
869           {
870             Thread.sleep(1000);
871           } catch (InterruptedException e)
872           {
873             // TODO Auto-generated catch block
874             e.printStackTrace();
875           }
876           i++;
877         }
878       }
879
880       JarOutputStream jout = new JarOutputStream(fos);
881       List<AlignFrame> frames = new ArrayList<>();
882
883       // resolve splitframes
884       if (af.getViewport().getCodingComplement() != null)
885       {
886         frames = ((SplitFrame) af.getSplitViewContainer()).getAlignFrames();
887       }
888       else
889       {
890         frames.add(af);
891       }
892       saveAllFrames(frames, jout);
893       try
894       {
895         jout.flush();
896       } catch (Exception foo)
897       {
898       }
899       jout.close();
900       boolean success = true;
901
902       if (doBackup)
903       {
904         backupfiles.setWriteSuccess(success);
905         success = backupfiles.rollBackupsAndRenameTempFile();
906       }
907
908       return success;
909     } catch (Exception ex)
910     {
911       errorMessage = "Couldn't Write alignment view to Jalview Archive - see error output for details";
912       ex.printStackTrace();
913       return false;
914     }
915   }
916
917   private void writeDatasetFor(Hashtable<String, AlignFrame> dsses,
918           String fileName, JarOutputStream jout)
919   {
920
921     for (String dssids : dsses.keySet())
922     {
923       AlignFrame _af = dsses.get(dssids);
924       String jfileName = fileName + " Dataset for " + _af.getTitle();
925       if (!jfileName.endsWith(".xml"))
926       {
927         jfileName = jfileName + ".xml";
928       }
929       saveState(_af.alignPanel, jfileName, true, jout, null);
930     }
931   }
932
933   /**
934    * create a JalviewModel from an alignment view and marshall it to a
935    * JarOutputStream
936    * 
937    * @param ap
938    *          panel to create jalview model for
939    * @param fileName
940    *          name of alignment panel written to output stream
941    * @param jout
942    *          jar output stream
943    * @param viewIds
944    * @param out
945    *          jar entry name
946    */
947   public JalviewModel saveState(AlignmentPanel ap, String fileName,
948           JarOutputStream jout, List<String> viewIds)
949   {
950     return saveState(ap, fileName, false, jout, viewIds);
951   }
952
953   /**
954    * create a JalviewModel from an alignment view and marshall it to a
955    * JarOutputStream
956    * 
957    * @param ap
958    *          panel to create jalview model for
959    * @param fileName
960    *          name of alignment panel written to output stream
961    * @param storeDS
962    *          when true, only write the dataset for the alignment, not the data
963    *          associated with the view.
964    * @param jout
965    *          jar output stream
966    * @param out
967    *          jar entry name
968    */
969   public JalviewModel saveState(AlignmentPanel ap, String fileName,
970           boolean storeDS, JarOutputStream jout, List<String> viewIds)
971   {
972     if (viewIds == null)
973     {
974       viewIds = new ArrayList<>();
975     }
976
977     initSeqRefs();
978
979     List<UserColourScheme> userColours = new ArrayList<>();
980
981     AlignViewport av = ap.av;
982     ViewportRanges vpRanges = av.getRanges();
983
984     final ObjectFactory objectFactory = new ObjectFactory();
985     JalviewModel object = objectFactory.createJalviewModel();
986     object.setVamsasModel(new VAMSAS());
987
988     // object.setCreationDate(new java.util.Date(System.currentTimeMillis()));
989     try
990     {
991       GregorianCalendar c = new GregorianCalendar();
992       DatatypeFactory datatypeFactory = DatatypeFactory.newInstance();
993       XMLGregorianCalendar now = datatypeFactory.newXMLGregorianCalendar(c);// gregorianCalendar);
994       object.setCreationDate(now);
995     } catch (DatatypeConfigurationException e)
996     {
997       jalview.bin.Console.errPrintln("error writing date: " + e.toString());
998     }
999     object.setVersion(Cache.getDefault("VERSION", "Development Build"));
1000
1001     /**
1002      * rjal is full height alignment, jal is actual alignment with full metadata
1003      * but excludes hidden sequences.
1004      */
1005     jalview.datamodel.AlignmentI rjal = av.getAlignment(), jal = rjal;
1006
1007     if (av.hasHiddenRows())
1008     {
1009       rjal = jal.getHiddenSequences().getFullAlignment();
1010     }
1011
1012     SequenceSet vamsasSet = new SequenceSet();
1013     Sequence vamsasSeq;
1014     // JalviewModelSequence jms = new JalviewModelSequence();
1015
1016     vamsasSet.setGapChar(jal.getGapCharacter() + "");
1017
1018     if (jal.getDataset() != null)
1019     {
1020       // dataset id is the dataset's hashcode
1021       vamsasSet.setDatasetId(getDatasetIdRef(jal.getDataset()));
1022       if (storeDS)
1023       {
1024         // switch jal and the dataset
1025         jal = jal.getDataset();
1026         rjal = jal;
1027       }
1028     }
1029     if (jal.getProperties() != null)
1030     {
1031       Enumeration en = jal.getProperties().keys();
1032       while (en.hasMoreElements())
1033       {
1034         String key = en.nextElement().toString();
1035         SequenceSetProperties ssp = new SequenceSetProperties();
1036         ssp.setKey(key);
1037         ssp.setValue(jal.getProperties().get(key).toString());
1038         // vamsasSet.addSequenceSetProperties(ssp);
1039         vamsasSet.getSequenceSetProperties().add(ssp);
1040       }
1041     }
1042
1043     JSeq jseq;
1044     Set<String> calcIdSet = new HashSet<>();
1045     // record the set of vamsas sequence XML POJO we create.
1046     HashMap<String, Sequence> vamsasSetIds = new HashMap<>();
1047     // SAVE SEQUENCES
1048     for (final SequenceI jds : rjal.getSequences())
1049     {
1050       final SequenceI jdatasq = jds.getDatasetSequence() == null ? jds
1051               : jds.getDatasetSequence();
1052       String id = seqHash(jds);
1053       if (vamsasSetIds.get(id) == null)
1054       {
1055         if (seqRefIds.get(id) != null && !storeDS)
1056         {
1057           // This happens for two reasons: 1. multiple views are being
1058           // serialised.
1059           // 2. the hashCode has collided with another sequence's code. This
1060           // DOES
1061           // HAPPEN! (PF00072.15.stk does this)
1062           // JBPNote: Uncomment to debug writing out of files that do not read
1063           // back in due to ArrayOutOfBoundExceptions.
1064           // jalview.bin.Console.errPrintln("vamsasSeq backref: "+id+"");
1065           // jalview.bin.Console.errPrintln(jds.getName()+"
1066           // "+jds.getStart()+"-"+jds.getEnd()+" "+jds.getSequenceAsString());
1067           // jalview.bin.Console.errPrintln("Hashcode: "+seqHash(jds));
1068           // SequenceI rsq = (SequenceI) seqRefIds.get(id + "");
1069           // jalview.bin.Console.errPrintln(rsq.getName()+"
1070           // "+rsq.getStart()+"-"+rsq.getEnd()+" "+rsq.getSequenceAsString());
1071           // jalview.bin.Console.errPrintln("Hashcode: "+seqHash(rsq));
1072         }
1073         else
1074         {
1075           vamsasSeq = createVamsasSequence(id, jds);
1076           // vamsasSet.addSequence(vamsasSeq);
1077           vamsasSet.getSequence().add(vamsasSeq);
1078           vamsasSetIds.put(id, vamsasSeq);
1079           seqRefIds.put(id, jds);
1080         }
1081       }
1082       jseq = new JSeq();
1083       jseq.setStart(jds.getStart());
1084       jseq.setEnd(jds.getEnd());
1085       jseq.setColour(av.getSequenceColour(jds).getRGB());
1086
1087       jseq.setId(id); // jseq id should be a string not a number
1088       if (!storeDS)
1089       {
1090         // Store any sequences this sequence represents
1091         if (av.hasHiddenRows())
1092         {
1093           // use rjal, contains the full height alignment
1094           jseq.setHidden(
1095                   av.getAlignment().getHiddenSequences().isHidden(jds));
1096
1097           if (av.isHiddenRepSequence(jds))
1098           {
1099             jalview.datamodel.SequenceI[] reps = av
1100                     .getRepresentedSequences(jds).getSequencesInOrder(rjal);
1101
1102             for (int h = 0; h < reps.length; h++)
1103             {
1104               if (reps[h] != jds)
1105               {
1106                 // jseq.addHiddenSequences(rjal.findIndex(reps[h]));
1107                 jseq.getHiddenSequences().add(rjal.findIndex(reps[h]));
1108               }
1109             }
1110           }
1111         }
1112         // mark sequence as reference - if it is the reference for this view
1113         if (jal.hasSeqrep())
1114         {
1115           jseq.setViewreference(jds == jal.getSeqrep());
1116         }
1117       }
1118
1119       // TODO: omit sequence features from each alignment view's XML dump if we
1120       // are storing dataset
1121       List<SequenceFeature> sfs = jds.getSequenceFeatures();
1122       for (SequenceFeature sf : sfs)
1123       {
1124         // Features features = new Features();
1125         Feature features = new Feature();
1126
1127         features.setBegin(sf.getBegin());
1128         features.setEnd(sf.getEnd());
1129         features.setDescription(sf.getDescription());
1130         features.setType(sf.getType());
1131         features.setFeatureGroup(sf.getFeatureGroup());
1132         features.setScore(sf.getScore());
1133         if (sf.links != null)
1134         {
1135           for (int l = 0; l < sf.links.size(); l++)
1136           {
1137             OtherData keyValue = new OtherData();
1138             keyValue.setKey("LINK_" + l);
1139             keyValue.setValue(sf.links.elementAt(l).toString());
1140             // features.addOtherData(keyValue);
1141             features.getOtherData().add(keyValue);
1142           }
1143         }
1144         if (sf.otherDetails != null)
1145         {
1146           /*
1147            * save feature attributes, which may be simple strings or
1148            * map valued (have sub-attributes)
1149            */
1150           for (Entry<String, Object> entry : sf.otherDetails.entrySet())
1151           {
1152             String key = entry.getKey();
1153             Object value = entry.getValue();
1154             if (value instanceof Map<?, ?>)
1155             {
1156               for (Entry<String, Object> subAttribute : ((Map<String, Object>) value)
1157                       .entrySet())
1158               {
1159                 OtherData otherData = new OtherData();
1160                 otherData.setKey(key);
1161                 otherData.setKey2(subAttribute.getKey());
1162                 otherData.setValue(subAttribute.getValue().toString());
1163                 // features.addOtherData(otherData);
1164                 features.getOtherData().add(otherData);
1165               }
1166             }
1167             else
1168             {
1169               OtherData otherData = new OtherData();
1170               otherData.setKey(key);
1171               otherData.setValue(value.toString());
1172               // features.addOtherData(otherData);
1173               features.getOtherData().add(otherData);
1174             }
1175           }
1176         }
1177
1178         // jseq.addFeatures(features);
1179         jseq.getFeatures().add(features);
1180       }
1181
1182       if (jdatasq.getAllPDBEntries() != null)
1183       {
1184         Enumeration<PDBEntry> en = jdatasq.getAllPDBEntries().elements();
1185         while (en.hasMoreElements())
1186         {
1187           Pdbids pdb = new Pdbids();
1188           jalview.datamodel.PDBEntry entry = en.nextElement();
1189
1190           String pdbId = entry.getId();
1191           pdb.setId(pdbId);
1192           pdb.setType(entry.getType());
1193
1194           /*
1195            * Store any structure views associated with this sequence. This
1196            * section copes with duplicate entries in the project, so a dataset
1197            * only view *should* be coped with sensibly.
1198            */
1199           // This must have been loaded, is it still visible?
1200           List<JalviewStructureDisplayI> viewFrames = new ArrayList<>();
1201           if (Desktop.desktop != null)
1202           {
1203             JInternalFrame[] jifs = Desktop.desktop.getAllFrames();
1204             if (jifs != null)
1205             {
1206               for (JInternalFrame jif : jifs)
1207               {
1208                 if (jif instanceof JalviewStructureDisplayI)
1209                 {
1210                   viewFrames.add((JalviewStructureDisplayI) jif);
1211                 }
1212               }
1213             }
1214           }
1215           else if (Jalview.isHeadlessMode()
1216                   && Jalview.getInstance().getCommands() != null)
1217           {
1218             viewFrames.addAll(
1219                     StructureViewerBase.getAllStructureViewerBases());
1220           }
1221
1222           String matchedFile = null;
1223           for (JalviewStructureDisplayI viewFrame : viewFrames)
1224           {
1225             matchedFile = saveStructureViewer(ap, jds, pdb, entry, viewIds,
1226                     matchedFile, viewFrame);
1227             /*
1228              * Only store each structure viewer's state once in the project
1229              * jar. First time through only (storeDS==false)
1230              */
1231             String viewId = viewFrame.getViewId();
1232             String viewerType = viewFrame.getViewerType().toString();
1233             if (!storeDS && !viewIds.contains(viewId))
1234             {
1235               viewIds.add(viewId);
1236               File viewerState = viewFrame.saveSession();
1237               if (viewerState != null)
1238               {
1239                 copyFileToJar(jout, viewerState.getPath(),
1240                         getViewerJarEntryName(viewId), viewerType);
1241               }
1242               else
1243               {
1244                 Console.error(
1245                         "Failed to save viewer state for " + viewerType);
1246               }
1247             }
1248           }
1249
1250           if (matchedFile != null || entry.getFile() != null)
1251           {
1252             if (entry.getFile() != null)
1253             {
1254               // use entry's file
1255               matchedFile = entry.getFile();
1256             }
1257             pdb.setFile(matchedFile); // entry.getFile());
1258             if (pdbfiles == null)
1259             {
1260               pdbfiles = new ArrayList<>();
1261             }
1262
1263             if (!pdbfiles.contains(pdbId))
1264             {
1265               pdbfiles.add(pdbId);
1266               copyFileToJar(jout, matchedFile, pdbId, pdbId);
1267             }
1268           }
1269
1270           Enumeration<String> props = entry.getProperties();
1271           if (props.hasMoreElements())
1272           {
1273             // PdbentryItem item = new PdbentryItem();
1274             while (props.hasMoreElements())
1275             {
1276               Property prop = new Property();
1277               String key = props.nextElement();
1278               prop.setName(key);
1279               prop.setValue(entry.getProperty(key).toString());
1280               // item.addProperty(prop);
1281               pdb.getProperty().add(prop);
1282             }
1283             // pdb.addPdbentryItem(item);
1284           }
1285
1286           // jseq.addPdbids(pdb);
1287           jseq.getPdbids().add(pdb);
1288         }
1289       }
1290
1291       saveRnaViewers(jout, jseq, jds, viewIds, ap, storeDS);
1292
1293       // jms.addJSeq(jseq);
1294       object.getJSeq().add(jseq);
1295     }
1296
1297     if (!storeDS && av.hasHiddenRows())
1298     {
1299       jal = av.getAlignment();
1300     }
1301     // SAVE MAPPINGS
1302     // FOR DATASET
1303     if (storeDS && jal.getCodonFrames() != null)
1304     {
1305       List<AlignedCodonFrame> jac = jal.getCodonFrames();
1306       for (AlignedCodonFrame acf : jac)
1307       {
1308         AlcodonFrame alc = new AlcodonFrame();
1309         if (acf.getProtMappings() != null
1310                 && acf.getProtMappings().length > 0)
1311         {
1312           boolean hasMap = false;
1313           SequenceI[] dnas = acf.getdnaSeqs();
1314           jalview.datamodel.Mapping[] pmaps = acf.getProtMappings();
1315           for (int m = 0; m < pmaps.length; m++)
1316           {
1317             AlcodMap alcmap = new AlcodMap();
1318             alcmap.setDnasq(seqHash(dnas[m]));
1319             alcmap.setMapping(
1320                     createVamsasMapping(pmaps[m], dnas[m], null, false));
1321             // alc.addAlcodMap(alcmap);
1322             alc.getAlcodMap().add(alcmap);
1323             hasMap = true;
1324           }
1325           if (hasMap)
1326           {
1327             // vamsasSet.addAlcodonFrame(alc);
1328             vamsasSet.getAlcodonFrame().add(alc);
1329           }
1330         }
1331         // TODO: delete this ? dead code from 2.8.3->2.9 ?
1332         // {
1333         // AlcodonFrame alc = new AlcodonFrame();
1334         // vamsasSet.addAlcodonFrame(alc);
1335         // for (int p = 0; p < acf.aaWidth; p++)
1336         // {
1337         // Alcodon cmap = new Alcodon();
1338         // if (acf.codons[p] != null)
1339         // {
1340         // // Null codons indicate a gapped column in the translated peptide
1341         // // alignment.
1342         // cmap.setPos1(acf.codons[p][0]);
1343         // cmap.setPos2(acf.codons[p][1]);
1344         // cmap.setPos3(acf.codons[p][2]);
1345         // }
1346         // alc.addAlcodon(cmap);
1347         // }
1348         // if (acf.getProtMappings() != null
1349         // && acf.getProtMappings().length > 0)
1350         // {
1351         // SequenceI[] dnas = acf.getdnaSeqs();
1352         // jalview.datamodel.Mapping[] pmaps = acf.getProtMappings();
1353         // for (int m = 0; m < pmaps.length; m++)
1354         // {
1355         // AlcodMap alcmap = new AlcodMap();
1356         // alcmap.setDnasq(seqHash(dnas[m]));
1357         // alcmap.setMapping(createVamsasMapping(pmaps[m], dnas[m], null,
1358         // false));
1359         // alc.addAlcodMap(alcmap);
1360         // }
1361         // }
1362       }
1363     }
1364
1365     // SAVE TREES
1366     // /////////////////////////////////
1367     if (!storeDS && av.getCurrentTree() != null)
1368     {
1369       // FIND ANY ASSOCIATED TREES
1370       // NOT IMPLEMENTED FOR HEADLESS STATE AT PRESENT
1371       if (Desktop.desktop != null)
1372       {
1373         JInternalFrame[] frames = Desktop.desktop.getAllFrames();
1374
1375         for (int t = 0; t < frames.length; t++)
1376         {
1377           if (frames[t] instanceof TreePanel)
1378           {
1379             TreePanel tp = (TreePanel) frames[t];
1380
1381             if (tp.getTreeCanvas().getViewport().getAlignment() == jal)
1382             {
1383               JalviewModel.Tree tree = new JalviewModel.Tree();
1384               tree.setTitle(tp.getTitle());
1385               tree.setCurrentTree((av.getCurrentTree() == tp.getTree()));
1386               tree.setNewick(tp.getTree().print());
1387               tree.setThreshold(tp.getTreeCanvas().getThreshold());
1388
1389               tree.setFitToWindow(tp.fitToWindow.getState());
1390               tree.setFontName(tp.getTreeFont().getName());
1391               tree.setFontSize(tp.getTreeFont().getSize());
1392               tree.setFontStyle(tp.getTreeFont().getStyle());
1393               tree.setMarkUnlinked(tp.placeholdersMenu.getState());
1394
1395               tree.setShowBootstrap(tp.bootstrapMenu.getState());
1396               tree.setShowDistances(tp.distanceMenu.getState());
1397
1398               tree.setHeight(tp.getHeight());
1399               tree.setWidth(tp.getWidth());
1400               tree.setXpos(tp.getX());
1401               tree.setYpos(tp.getY());
1402               tree.setId(makeHashCode(tp, null));
1403               tree.setLinkToAllViews(
1404                       tp.getTreeCanvas().isApplyToAllViews());
1405
1406               // columnWiseTree
1407               if (tp.isColumnWise())
1408               {
1409                 tree.setColumnWise(true);
1410                 String annId = tp.getAssocAnnotation().annotationId;
1411                 tree.setColumnReference(annId);
1412               }
1413               // jms.addTree(tree);
1414               object.getTree().add(tree);
1415             }
1416           }
1417         }
1418       }
1419     }
1420
1421     /*
1422      * save PCA viewers
1423      */
1424     if (!storeDS && Desktop.desktop != null)
1425     {
1426       for (JInternalFrame frame : Desktop.desktop.getAllFrames())
1427       {
1428         if (frame instanceof PCAPanel)
1429         {
1430           PCAPanel panel = (PCAPanel) frame;
1431           if (panel.getAlignViewport().getAlignment() == jal)
1432           {
1433             savePCA(panel, object);
1434           }
1435         }
1436         if (frame instanceof PaSiMapPanel)
1437         {
1438           PaSiMapPanel panel = (PaSiMapPanel) frame;
1439           if (panel.getAlignViewport().getAlignment() == jal)
1440           {
1441             savePaSiMap(panel, object);
1442           }
1443         }
1444       }
1445     }
1446
1447     // SAVE ANNOTATIONS
1448     /**
1449      * store forward refs from an annotationRow to any groups
1450      */
1451     IdentityHashMap<SequenceGroup, String> groupRefs = new IdentityHashMap<>();
1452     if (storeDS)
1453     {
1454       for (SequenceI sq : jal.getSequences())
1455       {
1456         // Store annotation on dataset sequences only
1457         AlignmentAnnotation[] aa = sq.getAnnotation();
1458         if (aa != null && aa.length > 0)
1459         {
1460           storeAlignmentAnnotation(aa, groupRefs, av, calcIdSet, storeDS,
1461                   vamsasSet);
1462         }
1463       }
1464     }
1465     else
1466     {
1467       if (jal.getAlignmentAnnotation() != null)
1468       {
1469         // Store the annotation shown on the alignment.
1470         AlignmentAnnotation[] aa = jal.getAlignmentAnnotation();
1471         storeAlignmentAnnotation(aa, groupRefs, av, calcIdSet, storeDS,
1472                 vamsasSet);
1473       }
1474     }
1475     // SAVE GROUPS
1476     if (jal.getGroups() != null)
1477     {
1478       JGroup[] groups = new JGroup[jal.getGroups().size()];
1479       int i = -1;
1480       for (jalview.datamodel.SequenceGroup sg : jal.getGroups())
1481       {
1482         JGroup jGroup = new JGroup();
1483         groups[++i] = jGroup;
1484
1485         jGroup.setStart(sg.getStartRes());
1486         jGroup.setEnd(sg.getEndRes());
1487         jGroup.setName(sg.getName());
1488         if (groupRefs.containsKey(sg))
1489         {
1490           // group has references so set its ID field
1491           jGroup.setId(groupRefs.get(sg));
1492         }
1493         ColourSchemeI colourScheme = sg.getColourScheme();
1494         if (colourScheme != null)
1495         {
1496           ResidueShaderI groupColourScheme = sg.getGroupColourScheme();
1497           if (groupColourScheme.conservationApplied())
1498           {
1499             jGroup.setConsThreshold(groupColourScheme.getConservationInc());
1500
1501             if (colourScheme instanceof jalview.schemes.UserColourScheme)
1502             {
1503               jGroup.setColour(setUserColourScheme(colourScheme,
1504                       userColours, object));
1505             }
1506             else
1507             {
1508               jGroup.setColour(colourScheme.getSchemeName());
1509             }
1510           }
1511           else if (colourScheme instanceof jalview.schemes.AnnotationColourGradient)
1512           {
1513             jGroup.setColour("AnnotationColourGradient");
1514             jGroup.setAnnotationColours(constructAnnotationColours(
1515                     (jalview.schemes.AnnotationColourGradient) colourScheme,
1516                     userColours, object));
1517           }
1518           else if (colourScheme instanceof jalview.schemes.UserColourScheme)
1519           {
1520             jGroup.setColour(
1521                     setUserColourScheme(colourScheme, userColours, object));
1522           }
1523           else
1524           {
1525             jGroup.setColour(colourScheme.getSchemeName());
1526           }
1527
1528           jGroup.setPidThreshold(groupColourScheme.getThreshold());
1529         }
1530
1531         jGroup.setOutlineColour(sg.getOutlineColour().getRGB());
1532         jGroup.setDisplayBoxes(sg.getDisplayBoxes());
1533         jGroup.setDisplayText(sg.getDisplayText());
1534         jGroup.setColourText(sg.getColourText());
1535         jGroup.setTextCol1(sg.textColour.getRGB());
1536         jGroup.setTextCol2(sg.textColour2.getRGB());
1537         jGroup.setTextColThreshold(sg.thresholdTextColour);
1538         jGroup.setShowUnconserved(sg.getShowNonconserved());
1539         jGroup.setIgnoreGapsinConsensus(sg.getIgnoreGapsConsensus());
1540         jGroup.setShowConsensusHistogram(sg.isShowConsensusHistogram());
1541         jGroup.setShowSequenceLogo(sg.isShowSequenceLogo());
1542         jGroup.setNormaliseSequenceLogo(sg.isNormaliseSequenceLogo());
1543         for (SequenceI seq : sg.getSequences())
1544         {
1545           // jGroup.addSeq(seqHash(seq));
1546           jGroup.getSeq().add(seqHash(seq));
1547         }
1548       }
1549
1550       // jms.setJGroup(groups);
1551       Object group;
1552       for (JGroup grp : groups)
1553       {
1554         object.getJGroup().add(grp);
1555       }
1556     }
1557     if (!storeDS)
1558     {
1559       // /////////SAVE VIEWPORT
1560       Viewport view = new Viewport();
1561       view.setTitle(ap.alignFrame.getTitle());
1562       view.setSequenceSetId(
1563               makeHashCode(av.getSequenceSetId(), av.getSequenceSetId()));
1564       view.setId(av.getViewId());
1565       if (av.getCodingComplement() != null)
1566       {
1567         view.setComplementId(av.getCodingComplement().getViewId());
1568       }
1569       view.setViewName(av.getViewName());
1570       view.setGatheredViews(av.isGatherViewsHere());
1571
1572       Rectangle size = ap.av.getExplodedGeometry();
1573       Rectangle position = size;
1574       if (size == null)
1575       {
1576         size = ap.alignFrame.getBounds();
1577         if (av.getCodingComplement() != null)
1578         {
1579           position = ((SplitFrame) ap.alignFrame.getSplitViewContainer())
1580                   .getBounds();
1581         }
1582         else
1583         {
1584           position = size;
1585         }
1586       }
1587       view.setXpos(position.x);
1588       view.setYpos(position.y);
1589
1590       view.setWidth(size.width);
1591       view.setHeight(size.height);
1592
1593       view.setStartRes(vpRanges.getStartRes());
1594       view.setStartSeq(vpRanges.getStartSeq());
1595
1596       OverviewPanel ov = ap.getOverviewPanel();
1597       if (ov != null)
1598       {
1599         Overview overview = new Overview();
1600         overview.setTitle(ov.getTitle());
1601         Rectangle bounds = ov.getFrameBounds();
1602         overview.setXpos(bounds.x);
1603         overview.setYpos(bounds.y);
1604         overview.setWidth(bounds.width);
1605         overview.setHeight(bounds.height);
1606         overview.setShowHidden(ov.isShowHiddenRegions());
1607         overview.setGapColour(ov.getCanvas().getGapColour().getRGB());
1608         overview.setResidueColour(
1609                 ov.getCanvas().getResidueColour().getRGB());
1610         overview.setHiddenColour(ov.getCanvas().getHiddenColour().getRGB());
1611         view.setOverview(overview);
1612       }
1613       if (av.getGlobalColourScheme() instanceof jalview.schemes.UserColourScheme)
1614       {
1615         view.setBgColour(setUserColourScheme(av.getGlobalColourScheme(),
1616                 userColours, object));
1617       }
1618       else if (av
1619               .getGlobalColourScheme() instanceof jalview.schemes.AnnotationColourGradient)
1620       {
1621         AnnotationColourScheme ac = constructAnnotationColours(
1622                 (jalview.schemes.AnnotationColourGradient) av
1623                         .getGlobalColourScheme(),
1624                 userColours, object);
1625
1626         view.setAnnotationColours(ac);
1627         view.setBgColour("AnnotationColourGradient");
1628       }
1629       else
1630       {
1631         view.setBgColour(ColourSchemeProperty
1632                 .getColourName(av.getGlobalColourScheme()));
1633       }
1634
1635       ResidueShaderI vcs = av.getResidueShading();
1636       ColourSchemeI cs = av.getGlobalColourScheme();
1637
1638       if (cs != null)
1639       {
1640         if (vcs.conservationApplied())
1641         {
1642           view.setConsThreshold(vcs.getConservationInc());
1643           if (cs instanceof jalview.schemes.UserColourScheme)
1644           {
1645             view.setBgColour(setUserColourScheme(cs, userColours, object));
1646           }
1647         }
1648         view.setPidThreshold(vcs.getThreshold());
1649       }
1650
1651       view.setConservationSelected(av.getConservationSelected());
1652       view.setPidSelected(av.getAbovePIDThreshold());
1653       view.setCharHeight(av.getCharHeight());
1654       view.setCharWidth(av.getCharWidth());
1655       final Font font = av.getFont();
1656       view.setFontName(font.getName());
1657       view.setFontSize(font.getSize());
1658       view.setFontStyle(font.getStyle());
1659       view.setScaleProteinAsCdna(av.getViewStyle().isScaleProteinAsCdna());
1660       view.setRenderGaps(av.isRenderGaps());
1661       view.setShowAnnotation(av.isShowAnnotation());
1662       view.setShowBoxes(av.getShowBoxes());
1663       view.setShowColourText(av.getColourText());
1664       view.setShowFullId(av.getShowJVSuffix());
1665       view.setRightAlignIds(av.isRightAlignIds());
1666       view.setIdWidth(av.getIdWidth());
1667       view.setIdWidthManuallyAdjusted(
1668               ap.getIdPanel().getIdCanvas().isManuallyAdjusted());
1669
1670       view.setShowSequenceFeatures(av.isShowSequenceFeatures());
1671       view.setShowText(av.getShowText());
1672       view.setShowUnconserved(av.getShowUnconserved());
1673       view.setWrapAlignment(av.getWrapAlignment());
1674       view.setTextCol1(av.getTextColour().getRGB());
1675       view.setTextCol2(av.getTextColour2().getRGB());
1676       view.setTextColThreshold(av.getThresholdTextColour());
1677       view.setShowConsensusHistogram(av.isShowConsensusHistogram());
1678       view.setShowSequenceLogo(av.isShowSequenceLogo());
1679       view.setNormaliseSequenceLogo(av.isNormaliseSequenceLogo());
1680       view.setShowGroupConsensus(av.isShowGroupConsensus());
1681       view.setShowGroupConservation(av.isShowGroupConservation());
1682       view.setShowNPfeatureTooltip(av.isShowNPFeats());
1683       view.setShowDbRefTooltip(av.isShowDBRefs());
1684       view.setFollowHighlight(av.isFollowHighlight());
1685       view.setFollowSelection(av.followSelection);
1686       view.setIgnoreGapsinConsensus(av.isIgnoreGapsConsensus());
1687       view.setShowComplementFeatures(av.isShowComplementFeatures());
1688       view.setShowComplementFeaturesOnTop(
1689               av.isShowComplementFeaturesOnTop());
1690       if (av.getFeaturesDisplayed() != null)
1691       {
1692         FeatureSettings fs = new FeatureSettings();
1693
1694         FeatureRendererModel fr = ap.getSeqPanel().seqCanvas
1695                 .getFeatureRenderer();
1696         String[] renderOrder = fr.getRenderOrder().toArray(new String[0]);
1697
1698         Vector<String> settingsAdded = new Vector<>();
1699         if (renderOrder != null)
1700         {
1701           for (String featureType : renderOrder)
1702           {
1703             FeatureSettings.Setting setting = new FeatureSettings.Setting();
1704             setting.setType(featureType);
1705
1706             /*
1707              * save any filter for the feature type
1708              */
1709             FeatureMatcherSetI filter = fr.getFeatureFilter(featureType);
1710             if (filter != null)
1711             {
1712               Iterator<FeatureMatcherI> filters = filter.getMatchers()
1713                       .iterator();
1714               FeatureMatcherI firstFilter = filters.next();
1715               setting.setMatcherSet(Jalview2XML.marshalFilter(firstFilter,
1716                       filters, filter.isAnded()));
1717             }
1718
1719             /*
1720              * save colour scheme for the feature type
1721              */
1722             FeatureColourI fcol = fr.getFeatureStyle(featureType);
1723             if (!fcol.isSimpleColour())
1724             {
1725               setting.setColour(fcol.getMaxColour().getRGB());
1726               setting.setMincolour(fcol.getMinColour().getRGB());
1727               setting.setMin(fcol.getMin());
1728               setting.setMax(fcol.getMax());
1729               setting.setColourByLabel(fcol.isColourByLabel());
1730               if (fcol.isColourByAttribute())
1731               {
1732                 String[] attName = fcol.getAttributeName();
1733                 setting.getAttributeName().add(attName[0]);
1734                 if (attName.length > 1)
1735                 {
1736                   setting.getAttributeName().add(attName[1]);
1737                 }
1738               }
1739               setting.setAutoScale(fcol.isAutoScaled());
1740               setting.setThreshold(fcol.getThreshold());
1741               Color noColour = fcol.getNoColour();
1742               if (noColour == null)
1743               {
1744                 setting.setNoValueColour(NoValueColour.NONE);
1745               }
1746               else if (noColour.equals(fcol.getMaxColour()))
1747               {
1748                 setting.setNoValueColour(NoValueColour.MAX);
1749               }
1750               else
1751               {
1752                 setting.setNoValueColour(NoValueColour.MIN);
1753               }
1754               // -1 = No threshold, 0 = Below, 1 = Above
1755               setting.setThreshstate(fcol.isAboveThreshold() ? 1
1756                       : (fcol.isBelowThreshold() ? 0 : -1));
1757             }
1758             else
1759             {
1760               setting.setColour(fcol.getColour().getRGB());
1761             }
1762
1763             setting.setDisplay(
1764                     av.getFeaturesDisplayed().isVisible(featureType));
1765             float rorder = fr.getOrder(featureType);
1766             if (rorder > -1)
1767             {
1768               setting.setOrder(rorder);
1769             }
1770             /// fs.addSetting(setting);
1771             fs.getSetting().add(setting);
1772             settingsAdded.addElement(featureType);
1773           }
1774         }
1775
1776         // is groups actually supposed to be a map here ?
1777         Iterator<String> en = fr.getFeatureGroups().iterator();
1778         Vector<String> groupsAdded = new Vector<>();
1779         while (en.hasNext())
1780         {
1781           String grp = en.next();
1782           if (groupsAdded.contains(grp))
1783           {
1784             continue;
1785           }
1786           Group g = new Group();
1787           g.setName(grp);
1788           g.setDisplay(((Boolean) fr.checkGroupVisibility(grp, false))
1789                   .booleanValue());
1790           // fs.addGroup(g);
1791           fs.getGroup().add(g);
1792           groupsAdded.addElement(grp);
1793         }
1794         // jms.setFeatureSettings(fs);
1795         object.setFeatureSettings(fs);
1796       }
1797
1798       if (av.hasHiddenColumns())
1799       {
1800         jalview.datamodel.HiddenColumns hidden = av.getAlignment()
1801                 .getHiddenColumns();
1802         if (hidden == null)
1803         {
1804           Console.warn(
1805                   "REPORT BUG: avoided null columnselection bug (DMAM reported). Please contact Jim about this.");
1806         }
1807         else
1808         {
1809           Iterator<int[]> hiddenRegions = hidden.iterator();
1810           while (hiddenRegions.hasNext())
1811           {
1812             int[] region = hiddenRegions.next();
1813             HiddenColumns hc = new HiddenColumns();
1814             hc.setStart(region[0]);
1815             hc.setEnd(region[1]);
1816             // view.addHiddenColumns(hc);
1817             view.getHiddenColumns().add(hc);
1818           }
1819         }
1820       }
1821       if (calcIdSet.size() > 0)
1822       {
1823         for (String calcId : calcIdSet)
1824         {
1825           if (calcId.trim().length() > 0)
1826           {
1827             CalcIdParam cidp = createCalcIdParam(calcId, av);
1828             // Some calcIds have no parameters.
1829             if (cidp != null)
1830             {
1831               // view.addCalcIdParam(cidp);
1832               view.getCalcIdParam().add(cidp);
1833             }
1834           }
1835         }
1836       }
1837
1838       // jms.addViewport(view);
1839       object.getViewport().add(view);
1840     }
1841
1842     if (storeDS)
1843     {
1844       // store matrices referenced by any views or annotation in this dataset
1845       if (xmlMatrices != null && xmlMatrices.size() > 0)
1846       {
1847         Console.debug(
1848                 "Adding " + xmlMatrices.size() + " matrices to dataset.");
1849         vamsasSet.getMatrix().addAll(xmlMatrices);
1850         xmlMatrices.clear();
1851       }
1852     }
1853
1854     // object.setJalviewModelSequence(jms);
1855     // object.getVamsasModel().addSequenceSet(vamsasSet);
1856     object.getVamsasModel().getSequenceSet().add(vamsasSet);
1857
1858     if (jout != null && fileName != null)
1859     {
1860       // We may not want to write the object to disk,
1861       // eg we can copy the alignViewport to a new view object
1862       // using save and then load
1863       try
1864       {
1865         fileName = fileName.replace('\\', '/');
1866         jalview.bin.Console.outPrintln("Writing jar entry " + fileName);
1867         JarEntry entry = new JarEntry(fileName);
1868         jout.putNextEntry(entry);
1869         PrintWriter pout = new PrintWriter(
1870                 new OutputStreamWriter(jout, UTF_8));
1871         JAXBContext jaxbContext = JAXBContext
1872                 .newInstance(JalviewModel.class);
1873         Marshaller jaxbMarshaller = jaxbContext.createMarshaller();
1874
1875         // output pretty printed
1876         // jaxbMarshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
1877         jaxbMarshaller.marshal(
1878                 new ObjectFactory().createJalviewModel(object), pout);
1879
1880         // jaxbMarshaller.marshal(object, pout);
1881         // marshaller.marshal(object);
1882         pout.flush();
1883         jout.closeEntry();
1884       } catch (Exception ex)
1885       {
1886         // TODO: raise error in GUI if marshalling failed.
1887         jalview.bin.Console.errPrintln("Error writing Jalview project");
1888         ex.printStackTrace();
1889       }
1890     }
1891     return object;
1892   }
1893
1894   /**
1895    * Writes PCA viewer attributes and computed values to an XML model object and
1896    * adds it to the JalviewModel. Any exceptions are reported by logging.
1897    */
1898   protected void savePCA(PCAPanel panel, JalviewModel object)
1899   {
1900     try
1901     {
1902       PcaViewer viewer = new PcaViewer();
1903       viewer.setHeight(panel.getHeight());
1904       viewer.setWidth(panel.getWidth());
1905       viewer.setXpos(panel.getX());
1906       viewer.setYpos(panel.getY());
1907       viewer.setTitle(panel.getTitle());
1908       PCAModel pcaModel = panel.getPcaModel();
1909       viewer.setScoreModelName(pcaModel.getScoreModelName());
1910       viewer.setXDim(panel.getSelectedDimensionIndex(X));
1911       viewer.setYDim(panel.getSelectedDimensionIndex(Y));
1912       viewer.setZDim(panel.getSelectedDimensionIndex(Z));
1913       viewer.setBgColour(
1914               panel.getRotatableCanvas().getBackgroundColour().getRGB());
1915       viewer.setScaleFactor(panel.getRotatableCanvas().getScaleFactor());
1916       float[] spMin = panel.getRotatableCanvas().getSeqMin();
1917       SeqPointMin spmin = new SeqPointMin();
1918       spmin.setXPos(spMin[0]);
1919       spmin.setYPos(spMin[1]);
1920       spmin.setZPos(spMin[2]);
1921       viewer.setSeqPointMin(spmin);
1922       float[] spMax = panel.getRotatableCanvas().getSeqMax();
1923       SeqPointMax spmax = new SeqPointMax();
1924       spmax.setXPos(spMax[0]);
1925       spmax.setYPos(spMax[1]);
1926       spmax.setZPos(spMax[2]);
1927       viewer.setSeqPointMax(spmax);
1928       viewer.setShowLabels(panel.getRotatableCanvas().isShowLabels());
1929       viewer.setLinkToAllViews(
1930               panel.getRotatableCanvas().isApplyToAllViews());
1931       SimilarityParamsI sp = pcaModel.getSimilarityParameters();
1932       viewer.setIncludeGaps(sp.includeGaps());
1933       viewer.setMatchGaps(sp.matchGaps());
1934       viewer.setIncludeGappedColumns(sp.includeGappedColumns());
1935       viewer.setDenominateByShortestLength(sp.denominateByShortestLength());
1936
1937       /*
1938        * sequence points on display
1939        */
1940       for (jalview.datamodel.SequencePoint spt : pcaModel
1941               .getSequencePoints())
1942       {
1943         SequencePoint point = new SequencePoint();
1944         point.setSequenceRef(seqHash(spt.getSequence()));
1945         point.setXPos(spt.coord.x);
1946         point.setYPos(spt.coord.y);
1947         point.setZPos(spt.coord.z);
1948         viewer.getSequencePoint().add(point);
1949       }
1950
1951       /*
1952        * (end points of) axes on display
1953        */
1954       for (Point p : panel.getRotatableCanvas().getAxisEndPoints())
1955       {
1956
1957         Axis axis = new Axis();
1958         axis.setXPos(p.x);
1959         axis.setYPos(p.y);
1960         axis.setZPos(p.z);
1961         viewer.getAxis().add(axis);
1962       }
1963
1964       /*
1965        * raw PCA data (note we are not restoring PCA inputs here -
1966        * alignment view, score model, similarity parameters)
1967        */
1968       PcaDataType data = new PcaDataType();
1969       viewer.setPcaData(data);
1970       PCA pca = pcaModel.getPcaData();
1971
1972       DoubleMatrix pm = new DoubleMatrix();
1973       saveDoubleMatrix(pca.getPairwiseScores(), pm);
1974       data.setPairwiseMatrix(pm);
1975
1976       DoubleMatrix tm = new DoubleMatrix();
1977       saveDoubleMatrix(pca.getTridiagonal(), tm);
1978       data.setTridiagonalMatrix(tm);
1979
1980       DoubleMatrix eigenMatrix = new DoubleMatrix();
1981       data.setEigenMatrix(eigenMatrix);
1982       saveDoubleMatrix(pca.getEigenmatrix(), eigenMatrix);
1983
1984       object.getPcaViewer().add(viewer);
1985     } catch (Throwable t)
1986     {
1987       Console.error("Error saving PCA: " + t.getMessage());
1988     }
1989   }
1990
1991   /**
1992    * Writes PaSiMap viewer attributes and computed values to an XML model object and
1993    * adds it to the JalviewModel. Any exceptions are reported by logging.
1994    * uses the functions from PCA
1995    */
1996   protected void savePaSiMap(PaSiMapPanel panel, JalviewModel object)
1997   {
1998     // TODO: this should be merged with above savePCAPanel - otherwise it is essentially redundant code
1999     try
2000     {
2001       PcaViewer viewer = new PcaViewer();
2002       viewer.setHeight(panel.getHeight());
2003       viewer.setWidth(panel.getWidth());
2004       viewer.setXpos(panel.getX());
2005       viewer.setYpos(panel.getY());
2006       viewer.setTitle(panel.getTitle());
2007       PaSiMapModel pasimapModel = panel.getPasimapModel();
2008       viewer.setScoreModelName(pasimapModel.getScoreModelName());
2009       viewer.setXDim(panel.getSelectedDimensionIndex(X));
2010       viewer.setYDim(panel.getSelectedDimensionIndex(Y));
2011       viewer.setZDim(panel.getSelectedDimensionIndex(Z));
2012       viewer.setBgColour(
2013               panel.getRotatableCanvas().getBackgroundColour().getRGB());
2014       viewer.setScaleFactor(panel.getRotatableCanvas().getScaleFactor());
2015       float[] spMin = panel.getRotatableCanvas().getSeqMin();
2016       SeqPointMin spmin = new SeqPointMin();
2017       spmin.setXPos(spMin[0]);
2018       spmin.setYPos(spMin[1]);
2019       spmin.setZPos(spMin[2]);
2020       viewer.setSeqPointMin(spmin);
2021       float[] spMax = panel.getRotatableCanvas().getSeqMax();
2022       SeqPointMax spmax = new SeqPointMax();
2023       spmax.setXPos(spMax[0]);
2024       spmax.setYPos(spMax[1]);
2025       spmax.setZPos(spMax[2]);
2026       viewer.setSeqPointMax(spmax);
2027       viewer.setShowLabels(panel.getRotatableCanvas().isShowLabels());
2028       viewer.setLinkToAllViews(
2029               panel.getRotatableCanvas().isApplyToAllViews());
2030       /* NOT FOR PASIMAP CALCULATIONS
2031        * 
2032       SimilarityParamsI sp = pasimapModel.getSimilarityParameters();
2033       viewer.setIncludeGaps(sp.includeGaps());
2034       viewer.setMatchGaps(sp.matchGaps());
2035       viewer.setIncludeGappedColumns(sp.includeGappedColumns());
2036       viewer.setDenominateByShortestLength(sp.denominateByShortestLength());
2037       */
2038
2039       /*
2040        * sequence points on display
2041        */
2042       for (jalview.datamodel.SequencePoint spt : pasimapModel
2043               .getSequencePoints())
2044       {
2045         SequencePoint point = new SequencePoint();
2046         point.setSequenceRef(seqHash(spt.getSequence()));
2047         point.setXPos(spt.coord.x);
2048         point.setYPos(spt.coord.y);
2049         point.setZPos(spt.coord.z);
2050         viewer.getSequencePoint().add(point);
2051       }
2052
2053       /*
2054        * (end points of) axes on display
2055        */
2056       for (Point p : panel.getRotatableCanvas().getAxisEndPoints())
2057       {
2058
2059         Axis axis = new Axis();
2060         axis.setXPos(p.x);
2061         axis.setYPos(p.y);
2062         axis.setZPos(p.z);
2063         viewer.getAxis().add(axis);
2064       }
2065
2066       /*
2067        * raw PaSiMap data (note we are not restoring PaSiMap inputs here -
2068        * alignment view, score model, similarity parameters)
2069        */
2070       PcaDataType data = new PcaDataType();
2071       viewer.setPcaData(data);
2072       PaSiMap pasimap = pasimapModel.getPasimapData();
2073
2074       DoubleMatrix pm = new DoubleMatrix();
2075       saveDoubleMatrix(pasimap.getPairwiseScores(), pm);
2076       data.setPairwiseMatrix(pm);
2077
2078       DoubleMatrix eigenMatrix = new DoubleMatrix();
2079       data.setEigenMatrix(eigenMatrix);
2080       saveDoubleMatrix(pasimap.getEigenmatrix(), eigenMatrix);
2081
2082       object.getPcaViewer().add(viewer);
2083     } catch (Throwable t)
2084     {
2085       Console.error("Error saving PaSiMap: " + t.getMessage());
2086     }
2087   }
2088   /**
2089    * Stores values from a matrix into an XML element, including (if present) the
2090    * D or E vectors
2091    * 
2092    * @param m
2093    * @param xmlMatrix
2094    * @see #loadDoubleMatrix(DoubleMatrix)
2095    */
2096   protected void saveDoubleMatrix(MatrixI m, DoubleMatrix xmlMatrix)
2097   {
2098     xmlMatrix.setRows(m.height());
2099     xmlMatrix.setColumns(m.width());
2100     for (int i = 0; i < m.height(); i++)
2101     {
2102       DoubleVector row = new DoubleVector();
2103       for (int j = 0; j < m.width(); j++)
2104       {
2105         row.getV().add(m.getValue(i, j));
2106       }
2107       xmlMatrix.getRow().add(row);
2108     }
2109     if (m.getD() != null)
2110     {
2111       DoubleVector dVector = new DoubleVector();
2112       for (double d : m.getD())
2113       {
2114         dVector.getV().add(d);
2115       }
2116       xmlMatrix.setD(dVector);
2117     }
2118     if (m.getE() != null)
2119     {
2120       DoubleVector eVector = new DoubleVector();
2121       for (double e : m.getE())
2122       {
2123         eVector.getV().add(e);
2124       }
2125       xmlMatrix.setE(eVector);
2126     }
2127   }
2128
2129   /**
2130    * Loads XML matrix data into a new Matrix object, including the D and/or E
2131    * vectors (if present)
2132    * 
2133    * @param mData
2134    * @return
2135    * @see Jalview2XML#saveDoubleMatrix(MatrixI, DoubleMatrix)
2136    */
2137   protected MatrixI loadDoubleMatrix(DoubleMatrix mData)
2138   {
2139     int rows = mData.getRows();
2140     double[][] vals = new double[rows][];
2141
2142     for (int i = 0; i < rows; i++)
2143     {
2144       List<Double> dVector = mData.getRow().get(i).getV();
2145       vals[i] = new double[dVector.size()];
2146       int dvi = 0;
2147       for (Double d : dVector)
2148       {
2149         vals[i][dvi++] = d;
2150       }
2151     }
2152
2153     MatrixI m = new Matrix(vals);
2154
2155     if (mData.getD() != null)
2156     {
2157       List<Double> dVector = mData.getD().getV();
2158       double[] vec = new double[dVector.size()];
2159       int dvi = 0;
2160       for (Double d : dVector)
2161       {
2162         vec[dvi++] = d;
2163       }
2164       m.setD(vec);
2165     }
2166     if (mData.getE() != null)
2167     {
2168       List<Double> dVector = mData.getE().getV();
2169       double[] vec = new double[dVector.size()];
2170       int dvi = 0;
2171       for (Double d : dVector)
2172       {
2173         vec[dvi++] = d;
2174       }
2175       m.setE(vec);
2176     }
2177
2178     return m;
2179   }
2180
2181   /**
2182    * Save any Varna viewers linked to this sequence. Writes an rnaViewer element
2183    * for each viewer, with
2184    * <ul>
2185    * <li>viewer geometry (position, size, split pane divider location)</li>
2186    * <li>index of the selected structure in the viewer (currently shows gapped
2187    * or ungapped)</li>
2188    * <li>the id of the annotation holding RNA secondary structure</li>
2189    * <li>(currently only one SS is shown per viewer, may be more in future)</li>
2190    * </ul>
2191    * Varna viewer state is also written out (in native Varna XML) to separate
2192    * project jar entries. A separate entry is written for each RNA structure
2193    * displayed, with the naming convention
2194    * <ul>
2195    * <li>rna_viewId_sequenceId_annotationId_[gapped|trimmed]</li>
2196    * </ul>
2197    * 
2198    * @param jout
2199    * @param jseq
2200    * @param jds
2201    * @param viewIds
2202    * @param ap
2203    * @param storeDataset
2204    */
2205   protected void saveRnaViewers(JarOutputStream jout, JSeq jseq,
2206           final SequenceI jds, List<String> viewIds, AlignmentPanel ap,
2207           boolean storeDataset)
2208   {
2209     if (Desktop.desktop == null)
2210     {
2211       return;
2212     }
2213     JInternalFrame[] frames = Desktop.desktop.getAllFrames();
2214     for (int f = frames.length - 1; f > -1; f--)
2215     {
2216       if (frames[f] instanceof AppVarna)
2217       {
2218         AppVarna varna = (AppVarna) frames[f];
2219         /*
2220          * link the sequence to every viewer that is showing it and is linked to
2221          * its alignment panel
2222          */
2223         if (varna.isListeningFor(jds) && ap == varna.getAlignmentPanel())
2224         {
2225           String viewId = varna.getViewId();
2226           RnaViewer rna = new RnaViewer();
2227           rna.setViewId(viewId);
2228           rna.setTitle(varna.getTitle());
2229           rna.setXpos(varna.getX());
2230           rna.setYpos(varna.getY());
2231           rna.setWidth(varna.getWidth());
2232           rna.setHeight(varna.getHeight());
2233           rna.setDividerLocation(varna.getDividerLocation());
2234           rna.setSelectedRna(varna.getSelectedIndex());
2235           // jseq.addRnaViewer(rna);
2236           jseq.getRnaViewer().add(rna);
2237
2238           /*
2239            * Store each Varna panel's state once in the project per sequence.
2240            * First time through only (storeDataset==false)
2241            */
2242           // boolean storeSessions = false;
2243           // String sequenceViewId = viewId + seqsToIds.get(jds);
2244           // if (!storeDataset && !viewIds.contains(sequenceViewId))
2245           // {
2246           // viewIds.add(sequenceViewId);
2247           // storeSessions = true;
2248           // }
2249           for (RnaModel model : varna.getModels())
2250           {
2251             if (model.seq == jds)
2252             {
2253               /*
2254                * VARNA saves each view (sequence or alignment secondary
2255                * structure, gapped or trimmed) as a separate XML file
2256                */
2257               String jarEntryName = rnaSessions.get(model);
2258               if (jarEntryName == null)
2259               {
2260
2261                 String varnaStateFile = varna.getStateInfo(model.rna);
2262                 jarEntryName = RNA_PREFIX + viewId + "_" + nextCounter();
2263                 copyFileToJar(jout, varnaStateFile, jarEntryName, "Varna");
2264                 rnaSessions.put(model, jarEntryName);
2265               }
2266               SecondaryStructure ss = new SecondaryStructure();
2267               String annotationId = varna.getAnnotation(jds).annotationId;
2268               ss.setAnnotationId(annotationId);
2269               ss.setViewerState(jarEntryName);
2270               ss.setGapped(model.gapped);
2271               ss.setTitle(model.title);
2272               // rna.addSecondaryStructure(ss);
2273               rna.getSecondaryStructure().add(ss);
2274             }
2275           }
2276         }
2277       }
2278     }
2279   }
2280
2281   /**
2282    * Copy the contents of a file to a new entry added to the output jar
2283    * 
2284    * @param jout
2285    * @param infilePath
2286    * @param jarEntryName
2287    * @param msg
2288    *          additional identifying info to log to the console
2289    */
2290   protected void copyFileToJar(JarOutputStream jout, String infilePath,
2291           String jarEntryName, String msg)
2292   {
2293     try (InputStream is = new FileInputStream(infilePath))
2294     {
2295       File file = new File(infilePath);
2296       if (file.exists() && jout != null)
2297       {
2298         jalview.bin.Console.outPrintln(
2299                 "Writing jar entry " + jarEntryName + " (" + msg + ")");
2300         jout.putNextEntry(new JarEntry(jarEntryName));
2301         copyAll(is, jout);
2302         jout.closeEntry();
2303         // dis = new DataInputStream(new FileInputStream(file));
2304         // byte[] data = new byte[(int) file.length()];
2305         // dis.readFully(data);
2306         // writeJarEntry(jout, jarEntryName, data);
2307       }
2308     } catch (Exception ex)
2309     {
2310       ex.printStackTrace();
2311     }
2312   }
2313
2314   /**
2315    * Copies input to output, in 4K buffers; handles any data (text or binary)
2316    * 
2317    * @param in
2318    * @param out
2319    * @throws IOException
2320    */
2321   protected void copyAll(InputStream in, OutputStream out)
2322           throws IOException
2323   {
2324     byte[] buffer = new byte[4096];
2325     int bytesRead = 0;
2326     while ((bytesRead = in.read(buffer)) != -1)
2327     {
2328       out.write(buffer, 0, bytesRead);
2329     }
2330   }
2331
2332   /**
2333    * Save the state of a structure viewer
2334    * 
2335    * @param ap
2336    * @param jds
2337    * @param pdb
2338    *          the archive XML element under which to save the state
2339    * @param entry
2340    * @param viewIds
2341    * @param matchedFile
2342    * @param viewFrame
2343    * @return
2344    */
2345   protected String saveStructureViewer(AlignmentPanel ap, SequenceI jds,
2346           Pdbids pdb, PDBEntry entry, List<String> viewIds,
2347           String matchedFile, JalviewStructureDisplayI viewFrame)
2348   {
2349     final AAStructureBindingModel bindingModel = viewFrame.getBinding();
2350
2351     /*
2352      * Look for any bindings for this viewer to the PDB file of interest
2353      * (including part matches excluding chain id)
2354      */
2355     for (int peid = 0; peid < bindingModel.getPdbCount(); peid++)
2356     {
2357       final PDBEntry pdbentry = bindingModel.getPdbEntry(peid);
2358       final String pdbId = pdbentry.getId();
2359       if (!pdbId.equals(entry.getId()) && !(entry.getId().length() > 4
2360               && entry.getId().toLowerCase(Locale.ROOT)
2361                       .startsWith(pdbId.toLowerCase(Locale.ROOT))))
2362       {
2363         /*
2364          * not interested in a binding to a different PDB entry here
2365          */
2366         continue;
2367       }
2368       if (matchedFile == null)
2369       {
2370         matchedFile = pdbentry.getFile();
2371       }
2372       else if (!matchedFile.equals(pdbentry.getFile()))
2373       {
2374         Console.warn(
2375                 "Probably lost some PDB-Sequence mappings for this structure file (which apparently has same PDB Entry code): "
2376                         + pdbentry.getFile());
2377       }
2378       // record the
2379       // file so we
2380       // can get at it if the ID
2381       // match is ambiguous (e.g.
2382       // 1QIP==1qipA)
2383
2384       for (int smap = 0; smap < viewFrame.getBinding()
2385               .getSequence()[peid].length; smap++)
2386       {
2387         // if (jal.findIndex(jmol.jmb.sequence[peid][smap]) > -1)
2388         if (jds == viewFrame.getBinding().getSequence()[peid][smap])
2389         {
2390           StructureState state = new StructureState();
2391           state.setVisible(true);
2392           state.setXpos(viewFrame.getY());
2393           state.setYpos(viewFrame.getY());
2394           state.setWidth(viewFrame.getWidth());
2395           state.setHeight(viewFrame.getHeight());
2396           final String viewId = viewFrame.getViewId();
2397           state.setViewId(viewId);
2398           state.setAlignwithAlignPanel(viewFrame.isUsedforaligment(ap));
2399           state.setColourwithAlignPanel(viewFrame.isUsedForColourBy(ap));
2400           state.setColourByJmol(viewFrame.isColouredByViewer());
2401           state.setType(viewFrame.getViewerType().toString());
2402           // pdb.addStructureState(state);
2403           pdb.getStructureState().add(state);
2404         }
2405       }
2406     }
2407     return matchedFile;
2408   }
2409
2410   /**
2411    * Populates the AnnotationColourScheme xml for save. This captures the
2412    * settings of the options in the 'Colour by Annotation' dialog.
2413    * 
2414    * @param acg
2415    * @param userColours
2416    * @param jm
2417    * @return
2418    */
2419   private AnnotationColourScheme constructAnnotationColours(
2420           AnnotationColourGradient acg, List<UserColourScheme> userColours,
2421           JalviewModel jm)
2422   {
2423     AnnotationColourScheme ac = new AnnotationColourScheme();
2424     ac.setAboveThreshold(acg.getAboveThreshold());
2425     ac.setThreshold(acg.getAnnotationThreshold());
2426     // 2.10.2 save annotationId (unique) not annotation label
2427     ac.setAnnotation(acg.getAnnotation().annotationId);
2428     if (acg.getBaseColour() instanceof UserColourScheme)
2429     {
2430       ac.setColourScheme(
2431               setUserColourScheme(acg.getBaseColour(), userColours, jm));
2432     }
2433     else
2434     {
2435       ac.setColourScheme(
2436               ColourSchemeProperty.getColourName(acg.getBaseColour()));
2437     }
2438
2439     ac.setMaxColour(acg.getMaxColour().getRGB());
2440     ac.setMinColour(acg.getMinColour().getRGB());
2441     ac.setPerSequence(acg.isSeqAssociated());
2442     ac.setPredefinedColours(acg.isPredefinedColours());
2443     return ac;
2444   }
2445
2446   private void storeAlignmentAnnotation(AlignmentAnnotation[] aa,
2447           IdentityHashMap<SequenceGroup, String> groupRefs,
2448           AlignmentViewport av, Set<String> calcIdSet, boolean storeDS,
2449           SequenceSet vamsasSet)
2450   {
2451
2452     for (int i = 0; i < aa.length; i++)
2453     {
2454       Annotation an = new Annotation();
2455
2456       AlignmentAnnotation annotation = aa[i];
2457       if (annotation.annotationId != null)
2458       {
2459         annotationIds.put(annotation.annotationId, annotation);
2460       }
2461
2462       an.setId(annotation.annotationId);
2463
2464       an.setVisible(annotation.visible);
2465
2466       an.setDescription(annotation.description);
2467
2468       if (annotation.sequenceRef != null)
2469       {
2470         // 2.9 JAL-1781 xref on sequence id rather than name
2471         an.setSequenceRef(seqsToIds.get(annotation.sequenceRef));
2472       }
2473       if (annotation.groupRef != null)
2474       {
2475         String groupIdr = groupRefs.get(annotation.groupRef);
2476         if (groupIdr == null)
2477         {
2478           // make a locally unique String
2479           groupRefs.put(annotation.groupRef,
2480                   groupIdr = ("" + System.currentTimeMillis()
2481                           + annotation.groupRef.getName()
2482                           + groupRefs.size()));
2483         }
2484         an.setGroupRef(groupIdr.toString());
2485       }
2486
2487       // store all visualization attributes for annotation
2488       an.setGraphHeight(annotation.graphHeight);
2489       an.setCentreColLabels(annotation.centreColLabels);
2490       an.setScaleColLabels(annotation.scaleColLabel);
2491       an.setShowAllColLabels(annotation.showAllColLabels);
2492       an.setBelowAlignment(annotation.belowAlignment);
2493
2494       if (annotation.graph > 0)
2495       {
2496         an.setGraph(true);
2497         an.setGraphType(annotation.graph);
2498         an.setGraphGroup(annotation.graphGroup);
2499         if (annotation.getThreshold() != null)
2500         {
2501           ThresholdLine line = new ThresholdLine();
2502           line.setLabel(annotation.getThreshold().label);
2503           line.setValue(annotation.getThreshold().value);
2504           line.setColour(annotation.getThreshold().colour.getRGB());
2505           an.setThresholdLine(line);
2506         }
2507         if (annotation.graph == AlignmentAnnotation.CONTACT_MAP)
2508         {
2509           if (annotation.sequenceRef.getContactMaps() != null)
2510           {
2511             ContactMatrixI cm = annotation.sequenceRef
2512                     .getContactMatrixFor(annotation);
2513             if (cm != null)
2514             {
2515               storeMatrixFor(vamsasSet, an, annotation, cm);
2516             }
2517           }
2518         }
2519       }
2520       else
2521       {
2522         an.setGraph(false);
2523       }
2524
2525       an.setLabel(annotation.label);
2526
2527       if (annotation == av.getAlignmentQualityAnnot()
2528               || annotation == av.getAlignmentConservationAnnotation()
2529               || annotation == av.getAlignmentConsensusAnnotation()
2530               || annotation.autoCalculated)
2531       {
2532         // new way of indicating autocalculated annotation -
2533         an.setAutoCalculated(annotation.autoCalculated);
2534       }
2535       if (annotation.hasScore())
2536       {
2537         an.setScore(annotation.getScore());
2538       }
2539
2540       if (annotation.getCalcId() != null)
2541       {
2542         calcIdSet.add(annotation.getCalcId());
2543         an.setCalcId(annotation.getCalcId());
2544       }
2545       if (annotation.hasProperties())
2546       {
2547         for (String pr : annotation.getProperties())
2548         {
2549           jalview.xml.binding.jalview.Property prop = new jalview.xml.binding.jalview.Property();
2550           prop.setName(pr);
2551           prop.setValue(annotation.getProperty(pr));
2552           an.getProperty().add(prop);
2553         }
2554       }
2555
2556       AnnotationElement ae;
2557       if (annotation.annotations != null)
2558       {
2559         an.setScoreOnly(false);
2560         for (int a = 0; a < annotation.annotations.length; a++)
2561         {
2562           if ((annotation == null) || (annotation.annotations[a] == null))
2563           {
2564             continue;
2565           }
2566
2567           ae = new AnnotationElement();
2568           if (annotation.annotations[a].description != null)
2569           {
2570             ae.setDescription(annotation.annotations[a].description);
2571           }
2572           if (annotation.annotations[a].displayCharacter != null)
2573           {
2574             ae.setDisplayCharacter(
2575                     annotation.annotations[a].displayCharacter);
2576           }
2577
2578           if (!Float.isNaN(annotation.annotations[a].value))
2579           {
2580             ae.setValue(annotation.annotations[a].value);
2581           }
2582
2583           ae.setPosition(a);
2584           if (annotation.annotations[a].secondaryStructure > ' ')
2585           {
2586             ae.setSecondaryStructure(
2587                     annotation.annotations[a].secondaryStructure + "");
2588           }
2589
2590           if (annotation.annotations[a].colour != null
2591                   && annotation.annotations[a].colour != java.awt.Color.black)
2592           {
2593             ae.setColour(annotation.annotations[a].colour.getRGB());
2594           }
2595
2596           // an.addAnnotationElement(ae);
2597           an.getAnnotationElement().add(ae);
2598           if (annotation.autoCalculated)
2599           {
2600             // only write one non-null entry into the annotation row -
2601             // sufficient to get the visualization attributes necessary to
2602             // display data
2603             continue;
2604           }
2605         }
2606       }
2607       else
2608       {
2609         an.setScoreOnly(true);
2610       }
2611       if (!storeDS || (storeDS && !annotation.autoCalculated))
2612       {
2613         // skip autocalculated annotation - these are only provided for
2614         // alignments
2615         // vamsasSet.addAnnotation(an);
2616         vamsasSet.getAnnotation().add(an);
2617       }
2618     }
2619
2620   }
2621
2622   private void storeMatrixFor(SequenceSet root, Annotation an,
2623           AlignmentAnnotation annotation, ContactMatrixI cm)
2624   {
2625     String cmId = contactMatrices.get(cm);
2626     MatrixType xmlmat = null;
2627
2628     // first create an xml ref for the matrix data, if none exist
2629     if (cmId == null)
2630     {
2631       xmlmat = new MatrixType();
2632       xmlmat.setType(cm.getType());
2633       xmlmat.setRows(BigInteger.valueOf(cm.getWidth()));
2634       xmlmat.setCols(BigInteger.valueOf(cm.getHeight()));
2635       // consider using an opaque to/from -> allow instance to control
2636       // its representation ?
2637       xmlmat.setElements(ContactMatrix.contactToFloatString(cm));
2638       if (cm.hasGroups())
2639       {
2640         for (BitSet gp : cm.getGroups())
2641         {
2642           xmlmat.getGroups().add(stringifyBitset(gp));
2643         }
2644       }
2645       if (cm.hasTree())
2646       {
2647         // provenance object for tree ?
2648         xmlmat.getNewick().add(cm.getNewick());
2649         xmlmat.setTreeMethod(cm.getTreeMethod());
2650       }
2651       if (cm.hasCutHeight())
2652       {
2653         xmlmat.setCutHeight(cm.getCutHeight());
2654       }
2655       xmlmat.setId(cmId = "m" + contactMatrices.size()
2656               + System.currentTimeMillis());
2657       Console.trace("Matrix data stored :" + cmId);
2658       contactMatrices.put(cm, cmId);
2659       contactMatrixRefs.put(cmId, cm);
2660       xmlMatrices.add(xmlmat);
2661     }
2662     else
2663     {
2664       Console.trace("Existing Matrix stored :" + cmId);
2665     }
2666
2667     // now store mapping
2668
2669     MapOnAMatrixType xmlmatmapping = new MapOnAMatrixType();
2670     xmlmatmapping.setMatrix(cmId);
2671
2672     // Pretty much all matrices currently managed in this way are
2673     // mappableContactMatrixI implementations - but check anyway
2674     if (cm instanceof MappableContactMatrixI)
2675     {
2676       jalview.util.MapList mlst = ((MappableContactMatrixI) cm)
2677               .getMapFor(annotation.sequenceRef);
2678       if (mlst != null)
2679       {
2680         MapListType mp = new MapListType();
2681         List<int[]> r = mlst.getFromRanges();
2682         for (int[] range : r)
2683         {
2684           MapListFrom mfrom = new MapListFrom();
2685           mfrom.setStart(range[0]);
2686           mfrom.setEnd(range[1]);
2687           // mp.addMapListFrom(mfrom);
2688           mp.getMapListFrom().add(mfrom);
2689         }
2690         r = mlst.getToRanges();
2691         for (int[] range : r)
2692         {
2693           MapListTo mto = new MapListTo();
2694           mto.setStart(range[0]);
2695           mto.setEnd(range[1]);
2696           // mp.addMapListTo(mto);
2697           mp.getMapListTo().add(mto);
2698         }
2699         mp.setMapFromUnit(BigInteger.valueOf(mlst.getFromRatio()));
2700         mp.setMapToUnit(BigInteger.valueOf(mlst.getToRatio()));
2701         xmlmatmapping.setMapping(mp);
2702       }
2703     }
2704     // and add to model
2705     an.getContactmatrix().add(xmlmatmapping);
2706   }
2707
2708   private String stringifyBitset(BitSet gp)
2709   {
2710     StringBuilder sb = new StringBuilder();
2711     for (long val : gp.toLongArray())
2712     {
2713       if (sb.length() > 0)
2714       {
2715         sb.append(",");
2716       }
2717       sb.append(val);
2718     }
2719     return sb.toString();
2720   }
2721
2722   private BitSet deStringifyBitset(String stringified)
2723   {
2724     if ("".equals(stringified) || stringified == null)
2725     {
2726       return new BitSet();
2727     }
2728     String[] longvals = stringified.split(",");
2729     long[] newlongvals = new long[longvals.length];
2730     for (int lv = 0; lv < longvals.length; lv++)
2731     {
2732       try
2733       {
2734         newlongvals[lv] = Long.valueOf(longvals[lv]);
2735       } catch (Exception x)
2736       {
2737         errorMessage += "Couldn't destringify bitset from: '" + stringified
2738                 + "'";
2739         newlongvals[lv] = 0;
2740       }
2741     }
2742     return BitSet.valueOf(newlongvals);
2743
2744   }
2745
2746   private CalcIdParam createCalcIdParam(String calcId, AlignViewport av)
2747   {
2748     AutoCalcSetting settings = av.getCalcIdSettingsFor(calcId);
2749     if (settings != null)
2750     {
2751       CalcIdParam vCalcIdParam = new CalcIdParam();
2752       vCalcIdParam.setCalcId(calcId);
2753       // vCalcIdParam.addServiceURL(settings.getServiceURI());
2754       vCalcIdParam.getServiceURL().add(settings.getServiceURI());
2755       // generic URI allowing a third party to resolve another instance of the
2756       // service used for this calculation
2757       for (String url : settings.getServiceURLs())
2758       {
2759         // vCalcIdParam.addServiceURL(urls);
2760         vCalcIdParam.getServiceURL().add(url);
2761       }
2762       vCalcIdParam.setVersion("1.0");
2763       if (settings.getPreset() != null)
2764       {
2765         WsParamSetI setting = settings.getPreset();
2766         vCalcIdParam.setName(setting.getName());
2767         vCalcIdParam.setDescription(setting.getDescription());
2768       }
2769       else
2770       {
2771         vCalcIdParam.setName("");
2772         vCalcIdParam.setDescription("Last used parameters");
2773       }
2774       // need to be able to recover 1) settings 2) user-defined presets or
2775       // recreate settings from preset 3) predefined settings provided by
2776       // service - or settings that can be transferred (or discarded)
2777       vCalcIdParam.setParameters(
2778               settings.getWsParamFile().replace("\n", "|\\n|"));
2779       vCalcIdParam.setAutoUpdate(settings.isAutoUpdate());
2780       // todo - decide if updateImmediately is needed for any projects.
2781
2782       return vCalcIdParam;
2783     }
2784     return null;
2785   }
2786
2787   private boolean recoverCalcIdParam(CalcIdParam calcIdParam,
2788           AlignViewport av)
2789   {
2790     if (calcIdParam.getVersion().equals("1.0"))
2791     {
2792       final String[] calcIds = calcIdParam.getServiceURL()
2793               .toArray(new String[0]);
2794       Jws2Instance service = Jws2Discoverer.getDiscoverer()
2795               .getPreferredServiceFor(calcIds);
2796       if (service != null)
2797       {
2798         WsParamSetI parmSet = null;
2799         try
2800         {
2801           parmSet = service.getParamStore().parseServiceParameterFile(
2802                   calcIdParam.getName(), calcIdParam.getDescription(),
2803                   calcIds,
2804                   calcIdParam.getParameters().replace("|\\n|", "\n"));
2805         } catch (IOException x)
2806         {
2807           Console.warn("Couldn't parse parameter data for "
2808                   + calcIdParam.getCalcId(), x);
2809           return false;
2810         }
2811         List<ArgumentI> argList = null;
2812         if (calcIdParam.getName().length() > 0)
2813         {
2814           parmSet = service.getParamStore()
2815                   .getPreset(calcIdParam.getName());
2816           if (parmSet != null)
2817           {
2818             // TODO : check we have a good match with settings in AACon -
2819             // otherwise we'll need to create a new preset
2820           }
2821         }
2822         else
2823         {
2824           argList = parmSet.getArguments();
2825           parmSet = null;
2826         }
2827         AAConSettings settings = new AAConSettings(
2828                 calcIdParam.isAutoUpdate(), service, parmSet, argList);
2829         av.setCalcIdSettingsFor(calcIdParam.getCalcId(), settings,
2830                 calcIdParam.isNeedsUpdate());
2831         return true;
2832       }
2833       else
2834       {
2835         Console.warn(
2836                 "Cannot resolve a service for the parameters used in this project. Try configuring a JABAWS server.");
2837         return false;
2838       }
2839     }
2840     throw new Error(MessageManager.formatMessage(
2841             "error.unsupported_version_calcIdparam", new Object[]
2842             { calcIdParam.toString() }));
2843   }
2844
2845   /**
2846    * External mapping between jalview objects and objects yielding a valid and
2847    * unique object ID string. This is null for normal Jalview project IO, but
2848    * non-null when a jalview project is being read or written as part of a
2849    * vamsas session.
2850    */
2851   IdentityHashMap jv2vobj = null;
2852
2853   /**
2854    * Construct a unique ID for jvobj using either existing bindings or if none
2855    * exist, the result of the hashcode call for the object.
2856    * 
2857    * @param jvobj
2858    *          jalview data object
2859    * @return unique ID for referring to jvobj
2860    */
2861   private String makeHashCode(Object jvobj, String altCode)
2862   {
2863     if (jv2vobj != null)
2864     {
2865       Object id = jv2vobj.get(jvobj);
2866       if (id != null)
2867       {
2868         return id.toString();
2869       }
2870       // check string ID mappings
2871       if (jvids2vobj != null && jvobj instanceof String)
2872       {
2873         id = jvids2vobj.get(jvobj);
2874       }
2875       if (id != null)
2876       {
2877         return id.toString();
2878       }
2879       // give up and warn that something has gone wrong
2880       Console.warn(
2881               "Cannot find ID for object in external mapping : " + jvobj);
2882     }
2883     return altCode;
2884   }
2885
2886   /**
2887    * return local jalview object mapped to ID, if it exists
2888    * 
2889    * @param idcode
2890    *          (may be null)
2891    * @return null or object bound to idcode
2892    */
2893   private Object retrieveExistingObj(String idcode)
2894   {
2895     if (idcode != null && vobj2jv != null)
2896     {
2897       return vobj2jv.get(idcode);
2898     }
2899     return null;
2900   }
2901
2902   /**
2903    * binding from ID strings from external mapping table to jalview data model
2904    * objects.
2905    */
2906   private Hashtable vobj2jv;
2907
2908   private Sequence createVamsasSequence(String id, SequenceI jds)
2909   {
2910     return createVamsasSequence(true, id, jds, null);
2911   }
2912
2913   private Sequence createVamsasSequence(boolean recurse, String id,
2914           SequenceI jds, SequenceI parentseq)
2915   {
2916     Sequence vamsasSeq = new Sequence();
2917     vamsasSeq.setId(id);
2918     vamsasSeq.setName(jds.getName());
2919     vamsasSeq.setSequence(jds.getSequenceAsString());
2920     vamsasSeq.setDescription(jds.getDescription());
2921     List<DBRefEntry> dbrefs = null;
2922     if (jds.getDatasetSequence() != null)
2923     {
2924       vamsasSeq.setDsseqid(seqHash(jds.getDatasetSequence()));
2925     }
2926     else
2927     {
2928       // seqId==dsseqid so we can tell which sequences really are
2929       // dataset sequences only
2930       vamsasSeq.setDsseqid(id);
2931       dbrefs = jds.getDBRefs();
2932       if (parentseq == null)
2933       {
2934         parentseq = jds;
2935       }
2936     }
2937
2938     /*
2939      * save any dbrefs; special subclass GeneLocus is flagged as 'locus'
2940      */
2941     if (dbrefs != null)
2942     {
2943       for (int d = 0, nd = dbrefs.size(); d < nd; d++)
2944       {
2945         DBRef dbref = new DBRef();
2946         DBRefEntry ref = dbrefs.get(d);
2947         dbref.setSource(ref.getSource());
2948         dbref.setVersion(ref.getVersion());
2949         dbref.setAccessionId(ref.getAccessionId());
2950         dbref.setCanonical(ref.isCanonical());
2951         if (ref instanceof GeneLocus)
2952         {
2953           dbref.setLocus(true);
2954         }
2955         if (ref.hasMap())
2956         {
2957           Mapping mp = createVamsasMapping(ref.getMap(), parentseq, jds,
2958                   recurse);
2959           dbref.setMapping(mp);
2960         }
2961         vamsasSeq.getDBRef().add(dbref);
2962       }
2963     }
2964     return vamsasSeq;
2965   }
2966
2967   private Mapping createVamsasMapping(jalview.datamodel.Mapping jmp,
2968           SequenceI parentseq, SequenceI jds, boolean recurse)
2969   {
2970     Mapping mp = null;
2971     if (jmp.getMap() != null)
2972     {
2973       mp = new Mapping();
2974
2975       jalview.util.MapList mlst = jmp.getMap();
2976       List<int[]> r = mlst.getFromRanges();
2977       for (int[] range : r)
2978       {
2979         MapListFrom mfrom = new MapListFrom();
2980         mfrom.setStart(range[0]);
2981         mfrom.setEnd(range[1]);
2982         // mp.addMapListFrom(mfrom);
2983         mp.getMapListFrom().add(mfrom);
2984       }
2985       r = mlst.getToRanges();
2986       for (int[] range : r)
2987       {
2988         MapListTo mto = new MapListTo();
2989         mto.setStart(range[0]);
2990         mto.setEnd(range[1]);
2991         // mp.addMapListTo(mto);
2992         mp.getMapListTo().add(mto);
2993       }
2994       mp.setMapFromUnit(BigInteger.valueOf(mlst.getFromRatio()));
2995       mp.setMapToUnit(BigInteger.valueOf(mlst.getToRatio()));
2996       if (jmp.getTo() != null)
2997       {
2998         // MappingChoice mpc = new MappingChoice();
2999
3000         // check/create ID for the sequence referenced by getTo()
3001
3002         String jmpid = "";
3003         SequenceI ps = null;
3004         if (parentseq != jmp.getTo()
3005                 && parentseq.getDatasetSequence() != jmp.getTo())
3006         {
3007           // chaining dbref rather than a handshaking one
3008           jmpid = seqHash(ps = jmp.getTo());
3009         }
3010         else
3011         {
3012           jmpid = seqHash(ps = parentseq);
3013         }
3014         // mpc.setDseqFor(jmpid);
3015         mp.setDseqFor(jmpid);
3016         if (!seqRefIds.containsKey(jmpid))
3017         {
3018           Console.debug("creatign new DseqFor ID");
3019           seqRefIds.put(jmpid, ps);
3020         }
3021         else
3022         {
3023           Console.debug("reusing DseqFor ID");
3024         }
3025
3026         // mp.setMappingChoice(mpc);
3027       }
3028     }
3029     return mp;
3030   }
3031
3032   String setUserColourScheme(jalview.schemes.ColourSchemeI cs,
3033           List<UserColourScheme> userColours, JalviewModel jm)
3034   {
3035     String id = null;
3036     jalview.schemes.UserColourScheme ucs = (jalview.schemes.UserColourScheme) cs;
3037     boolean newucs = false;
3038     if (!userColours.contains(ucs))
3039     {
3040       userColours.add(ucs);
3041       newucs = true;
3042     }
3043     id = "ucs" + userColours.indexOf(ucs);
3044     if (newucs)
3045     {
3046       // actually create the scheme's entry in the XML model
3047       java.awt.Color[] colours = ucs.getColours();
3048       UserColours uc = new UserColours();
3049       // UserColourScheme jbucs = new UserColourScheme();
3050       JalviewUserColours jbucs = new JalviewUserColours();
3051
3052       for (int i = 0; i < colours.length; i++)
3053       {
3054         Colour col = new Colour();
3055         col.setName(ResidueProperties.aa[i]);
3056         col.setRGB(jalview.util.Format.getHexString(colours[i]));
3057         // jbucs.addColour(col);
3058         jbucs.getColour().add(col);
3059       }
3060       if (ucs.getLowerCaseColours() != null)
3061       {
3062         colours = ucs.getLowerCaseColours();
3063         for (int i = 0; i < colours.length; i++)
3064         {
3065           Colour col = new Colour();
3066           col.setName(ResidueProperties.aa[i].toLowerCase(Locale.ROOT));
3067           col.setRGB(jalview.util.Format.getHexString(colours[i]));
3068           // jbucs.addColour(col);
3069           jbucs.getColour().add(col);
3070         }
3071       }
3072
3073       uc.setId(id);
3074       uc.setUserColourScheme(jbucs);
3075       // jm.addUserColours(uc);
3076       jm.getUserColours().add(uc);
3077     }
3078
3079     return id;
3080   }
3081
3082   jalview.schemes.UserColourScheme getUserColourScheme(JalviewModel jm,
3083           String id)
3084   {
3085     List<UserColours> uc = jm.getUserColours();
3086     UserColours colours = null;
3087     /*
3088     for (int i = 0; i < uc.length; i++)
3089     {
3090       if (uc[i].getId().equals(id))
3091       {
3092         colours = uc[i];
3093         break;
3094       }
3095     }
3096     */
3097     for (UserColours c : uc)
3098     {
3099       if (c.getId().equals(id))
3100       {
3101         colours = c;
3102         break;
3103       }
3104     }
3105
3106     java.awt.Color[] newColours = new java.awt.Color[24];
3107
3108     for (int i = 0; i < 24; i++)
3109     {
3110       newColours[i] = new java.awt.Color(Integer.parseInt(
3111               // colours.getUserColourScheme().getColour(i).getRGB(), 16));
3112               colours.getUserColourScheme().getColour().get(i).getRGB(),
3113               16));
3114     }
3115
3116     jalview.schemes.UserColourScheme ucs = new jalview.schemes.UserColourScheme(
3117             newColours);
3118
3119     if (colours.getUserColourScheme().getColour().size()/*Count()*/ > 24)
3120     {
3121       newColours = new java.awt.Color[23];
3122       for (int i = 0; i < 23; i++)
3123       {
3124         newColours[i] = new java.awt.Color(
3125                 Integer.parseInt(colours.getUserColourScheme().getColour()
3126                         .get(i + 24).getRGB(), 16));
3127       }
3128       ucs.setLowerCaseColours(newColours);
3129     }
3130
3131     return ucs;
3132   }
3133
3134   /**
3135    * contains last error message (if any) encountered by XML loader.
3136    */
3137   String errorMessage = null;
3138
3139   /**
3140    * flag to control whether the Jalview2XML_V1 parser should be deferred to if
3141    * exceptions are raised during project XML parsing
3142    */
3143   public boolean attemptversion1parse = false;
3144
3145   /**
3146    * Load a jalview project archive from a jar file
3147    * 
3148    * @param file
3149    *          - HTTP URL or filename
3150    */
3151   public AlignFrame loadJalviewAlign(final Object file)
3152   {
3153
3154     jalview.gui.AlignFrame af = null;
3155
3156     try
3157     {
3158       // create list to store references for any new Jmol viewers created
3159       newStructureViewers = new Vector<>();
3160       // UNMARSHALLER SEEMS TO CLOSE JARINPUTSTREAM, MOST ANNOYING
3161       // Workaround is to make sure caller implements the JarInputStreamProvider
3162       // interface
3163       // so we can re-open the jar input stream for each entry.
3164
3165       jarInputStreamProvider jprovider = createjarInputStreamProvider(file);
3166       af = loadJalviewAlign(jprovider);
3167       if (af != null)
3168       {
3169         af.setMenusForViewport();
3170       }
3171     } catch (MalformedURLException e)
3172     {
3173       errorMessage = "Invalid URL format for '" + file + "'";
3174       reportErrors();
3175     } finally
3176     {
3177       try
3178       {
3179         SwingUtilities.invokeAndWait(new Runnable()
3180         {
3181           @Override
3182           public void run()
3183           {
3184             setLoadingFinishedForNewStructureViewers();
3185           }
3186         });
3187       } catch (Exception x)
3188       {
3189         jalview.bin.Console
3190                 .errPrintln("Error loading alignment: " + x.getMessage());
3191       }
3192     }
3193     return af;
3194   }
3195
3196   @SuppressWarnings("unused")
3197   private jarInputStreamProvider createjarInputStreamProvider(
3198           final Object ofile) throws MalformedURLException
3199   {
3200
3201     // BH 2018 allow for bytes already attached to File object
3202     try
3203     {
3204       String file = (ofile instanceof File
3205               ? ((File) ofile).getCanonicalPath()
3206               : ofile.toString());
3207       byte[] bytes = Platform.isJS() ? Platform.getFileBytes((File) ofile)
3208               : null;
3209       URL url = null;
3210       errorMessage = null;
3211       uniqueSetSuffix = null;
3212       seqRefIds = null;
3213       viewportsAdded.clear();
3214       frefedSequence = null;
3215
3216       if (HttpUtils.startsWithHttpOrHttps(file))
3217       {
3218         url = new URL(file);
3219       }
3220       final URL _url = url;
3221       return new jarInputStreamProvider()
3222       {
3223
3224         @Override
3225         public JarInputStream getJarInputStream() throws IOException
3226         {
3227           if (bytes != null)
3228           {
3229             // jalview.bin.Console.outPrintln("Jalview2XML: opening byte
3230             // jarInputStream for
3231             // bytes.length=" + bytes.length);
3232             return new JarInputStream(new ByteArrayInputStream(bytes));
3233           }
3234           if (_url != null)
3235           {
3236             // jalview.bin.Console.outPrintln("Jalview2XML: opening url
3237             // jarInputStream for "
3238             // + _url);
3239             return new JarInputStream(HttpUtils.openStream(_url));
3240           }
3241           else
3242           {
3243             // jalview.bin.Console.outPrintln("Jalview2XML: opening file
3244             // jarInputStream for
3245             // " + file);
3246             return new JarInputStream(new FileInputStream(file));
3247           }
3248         }
3249
3250         @Override
3251         public String getFilename()
3252         {
3253           return file;
3254         }
3255       };
3256     } catch (IOException e)
3257     {
3258       e.printStackTrace();
3259       return null;
3260     }
3261   }
3262
3263   /**
3264    * Recover jalview session from a jalview project archive. Caller may
3265    * initialise uniqueSetSuffix, seqRefIds, viewportsAdded and frefedSequence
3266    * themselves. Any null fields will be initialised with default values,
3267    * non-null fields are left alone.
3268    * 
3269    * @param jprovider
3270    * @return
3271    */
3272   public AlignFrame loadJalviewAlign(final jarInputStreamProvider jprovider)
3273   {
3274     errorMessage = null;
3275     if (uniqueSetSuffix == null)
3276     {
3277       uniqueSetSuffix = System.currentTimeMillis() % 100000 + "";
3278     }
3279     if (seqRefIds == null)
3280     {
3281       initSeqRefs();
3282     }
3283     AlignFrame af = null, _af = null;
3284     List<AlignFrame> toRepaint = new ArrayList<AlignFrame>();
3285     IdentityHashMap<AlignmentI, AlignmentI> importedDatasets = new IdentityHashMap<>();
3286     Map<String, AlignFrame> gatherToThisFrame = new HashMap<>();
3287     final String file = jprovider.getFilename();
3288     try
3289     {
3290       JarInputStream jin = null;
3291       JarEntry jarentry = null;
3292       int entryCount = 1;
3293
3294       do
3295       {
3296         jin = jprovider.getJarInputStream();
3297         for (int i = 0; i < entryCount; i++)
3298         {
3299           jarentry = jin.getNextJarEntry();
3300         }
3301
3302         if (jarentry != null && jarentry.getName().endsWith(".xml"))
3303         {
3304           JAXBContext jc = JAXBContext
3305                   .newInstance("jalview.xml.binding.jalview");
3306           XMLStreamReader streamReader = XMLInputFactory.newInstance()
3307                   .createXMLStreamReader(jin);
3308           javax.xml.bind.Unmarshaller um = jc.createUnmarshaller();
3309           JAXBElement<JalviewModel> jbe = um.unmarshal(streamReader,
3310                   JalviewModel.class);
3311           JalviewModel object = jbe.getValue();
3312
3313           if (true) // !skipViewport(object))
3314           {
3315             _af = loadFromObject(object, file, true, jprovider);
3316             if (_af != null && object.getViewport().size() > 0)
3317             // getJalviewModelSequence().getViewportCount() > 0)
3318             {
3319               toRepaint.add(_af);
3320               if (af == null)
3321               {
3322                 // store a reference to the first view
3323                 af = _af;
3324               }
3325               if (_af.getViewport().isGatherViewsHere())
3326               {
3327                 // if this is a gathered view, keep its reference since
3328                 // after gathering views, only this frame will remain
3329                 af = _af;
3330                 gatherToThisFrame.put(_af.getViewport().getSequenceSetId(),
3331                         _af);
3332               }
3333               // Save dataset to register mappings once all resolved
3334               importedDatasets.put(
3335                       af.getViewport().getAlignment().getDataset(),
3336                       af.getViewport().getAlignment().getDataset());
3337             }
3338           }
3339           entryCount++;
3340         }
3341         else if (jarentry != null)
3342         {
3343           // Some other file here.
3344           entryCount++;
3345         }
3346       } while (jarentry != null);
3347       jin.close();
3348       resolveFrefedSequences();
3349       for (AlignFrame alignFrame : toRepaint)
3350       {
3351         alignFrame.repaint();
3352       }
3353     } catch (IOException ex)
3354     {
3355       ex.printStackTrace();
3356       errorMessage = "Couldn't locate Jalview XML file : " + file;
3357       jalview.bin.Console.errPrintln(
3358               "Exception whilst loading jalview XML file : " + ex + "\n");
3359     } catch (Exception ex)
3360     {
3361       jalview.bin.Console
3362               .errPrintln("Parsing as Jalview Version 2 file failed.");
3363       ex.printStackTrace(System.err);
3364       if (attemptversion1parse)
3365       {
3366         // used to attempt to parse as V1 castor-generated xml
3367       }
3368       if (Desktop.instance != null)
3369       {
3370         Desktop.instance.stopLoading();
3371       }
3372       if (af != null)
3373       {
3374         jalview.bin.Console.outPrintln("Successfully loaded archive file");
3375         return af;
3376       }
3377       ex.printStackTrace();
3378
3379       jalview.bin.Console.errPrintln(
3380               "Exception whilst loading jalview XML file : " + ex + "\n");
3381     } catch (OutOfMemoryError e)
3382     {
3383       // Don't use the OOM Window here
3384       errorMessage = "Out of memory loading jalview XML file";
3385       jalview.bin.Console
3386               .errPrintln("Out of memory whilst loading jalview XML file");
3387       e.printStackTrace();
3388     }
3389
3390     /*
3391      * Regather multiple views (with the same sequence set id) to the frame (if
3392      * any) that is flagged as the one to gather to, i.e. convert them to tabbed
3393      * views instead of separate frames. Note this doesn't restore a state where
3394      * some expanded views in turn have tabbed views - the last "first tab" read
3395      * in will play the role of gatherer for all.
3396      */
3397     for (AlignFrame fr : gatherToThisFrame.values())
3398     {
3399       Desktop.instance.gatherViews(fr);
3400     }
3401
3402     restoreSplitFrames();
3403     for (AlignmentI ds : importedDatasets.keySet())
3404     {
3405       if (ds.getCodonFrames() != null)
3406       {
3407         StructureSelectionManager
3408                 .getStructureSelectionManager(Desktop.instance)
3409                 .registerMappings(ds.getCodonFrames());
3410       }
3411     }
3412     if (errorMessage != null)
3413     {
3414       reportErrors();
3415     }
3416
3417     if (Desktop.instance != null)
3418     {
3419       Desktop.instance.stopLoading();
3420     }
3421
3422     return af;
3423   }
3424
3425   /**
3426    * Try to reconstruct and display SplitFrame windows, where each contains
3427    * complementary dna and protein alignments. Done by pairing up AlignFrame
3428    * objects (created earlier) which have complementary viewport ids associated.
3429    */
3430   protected void restoreSplitFrames()
3431   {
3432     List<SplitFrame> gatherTo = new ArrayList<>();
3433     List<AlignFrame> addedToSplitFrames = new ArrayList<>();
3434     Map<String, AlignFrame> dna = new HashMap<>();
3435
3436     /*
3437      * Identify the DNA alignments
3438      */
3439     for (Entry<Viewport, AlignFrame> candidate : splitFrameCandidates
3440             .entrySet())
3441     {
3442       AlignFrame af = candidate.getValue();
3443       if (af.getViewport().getAlignment().isNucleotide())
3444       {
3445         dna.put(candidate.getKey().getId(), af);
3446       }
3447     }
3448
3449     /*
3450      * Try to match up the protein complements
3451      */
3452     for (Entry<Viewport, AlignFrame> candidate : splitFrameCandidates
3453             .entrySet())
3454     {
3455       AlignFrame af = candidate.getValue();
3456       if (!af.getViewport().getAlignment().isNucleotide())
3457       {
3458         String complementId = candidate.getKey().getComplementId();
3459         // only non-null complements should be in the Map
3460         if (complementId != null && dna.containsKey(complementId))
3461         {
3462           final AlignFrame dnaFrame = dna.get(complementId);
3463           SplitFrame sf = createSplitFrame(dnaFrame, af);
3464           addedToSplitFrames.add(dnaFrame);
3465           addedToSplitFrames.add(af);
3466           dnaFrame.setMenusForViewport();
3467           af.setMenusForViewport();
3468           if (af.getViewport().isGatherViewsHere())
3469           {
3470             gatherTo.add(sf);
3471           }
3472         }
3473       }
3474     }
3475
3476     /*
3477      * Open any that we failed to pair up (which shouldn't happen!) as
3478      * standalone AlignFrame's.
3479      */
3480     for (Entry<Viewport, AlignFrame> candidate : splitFrameCandidates
3481             .entrySet())
3482     {
3483       AlignFrame af = candidate.getValue();
3484       if (!addedToSplitFrames.contains(af))
3485       {
3486         Viewport view = candidate.getKey();
3487         Desktop.addInternalFrame(af, view.getTitle(),
3488                 safeInt(view.getWidth()), safeInt(view.getHeight()));
3489         af.setMenusForViewport();
3490         jalview.bin.Console.errPrintln("Failed to restore view "
3491                 + view.getTitle() + " to split frame");
3492       }
3493     }
3494
3495     /*
3496      * Gather back into tabbed views as flagged.
3497      */
3498     for (SplitFrame sf : gatherTo)
3499     {
3500       Desktop.instance.gatherViews(sf);
3501     }
3502
3503     splitFrameCandidates.clear();
3504   }
3505
3506   /**
3507    * Construct and display one SplitFrame holding DNA and protein alignments.
3508    * 
3509    * @param dnaFrame
3510    * @param proteinFrame
3511    * @return
3512    */
3513   protected SplitFrame createSplitFrame(AlignFrame dnaFrame,
3514           AlignFrame proteinFrame)
3515   {
3516     SplitFrame splitFrame = new SplitFrame(dnaFrame, proteinFrame);
3517     String title = MessageManager.getString("label.linked_view_title");
3518     int width = (int) dnaFrame.getBounds().getWidth();
3519     int height = (int) (dnaFrame.getBounds().getHeight()
3520             + proteinFrame.getBounds().getHeight() + 50);
3521
3522     /*
3523      * SplitFrame location is saved to both enclosed frames
3524      */
3525     splitFrame.setLocation(dnaFrame.getX(), dnaFrame.getY());
3526     Desktop.addInternalFrame(splitFrame, title, width, height);
3527
3528     /*
3529      * And compute cDNA consensus (couldn't do earlier with consensus as
3530      * mappings were not yet present)
3531      */
3532     proteinFrame.getViewport().alignmentChanged(proteinFrame.alignPanel);
3533
3534     return splitFrame;
3535   }
3536
3537   /**
3538    * check errorMessage for a valid error message and raise an error box in the
3539    * GUI or write the current errorMessage to stderr and then clear the error
3540    * state.
3541    */
3542   protected void reportErrors()
3543   {
3544     reportErrors(false);
3545   }
3546
3547   protected void reportErrors(final boolean saving)
3548   {
3549     if (errorMessage != null)
3550     {
3551       final String finalErrorMessage = errorMessage;
3552       if (raiseGUI)
3553       {
3554         javax.swing.SwingUtilities.invokeLater(new Runnable()
3555         {
3556           @Override
3557           public void run()
3558           {
3559             JvOptionPane.showInternalMessageDialog(Desktop.desktop,
3560                     finalErrorMessage,
3561                     "Error " + (saving ? "saving" : "loading")
3562                             + " Jalview file",
3563                     JvOptionPane.WARNING_MESSAGE);
3564           }
3565         });
3566       }
3567       else
3568       {
3569         jalview.bin.Console.errPrintln(
3570                 "Problem loading Jalview file: " + errorMessage);
3571       }
3572     }
3573     errorMessage = null;
3574   }
3575
3576   Map<String, String> alreadyLoadedPDB = new HashMap<>();
3577
3578   /**
3579    * when set, local views will be updated from view stored in JalviewXML
3580    * Currently (28th Sep 2008) things will go horribly wrong in vamsas document
3581    * sync if this is set to true.
3582    */
3583   private final boolean updateLocalViews = false;
3584
3585   /**
3586    * Returns the path to a temporary file holding the PDB file for the given PDB
3587    * id. The first time of asking, searches for a file of that name in the
3588    * Jalview project jar, and copies it to a new temporary file. Any repeat
3589    * requests just return the path to the file previously created.
3590    * 
3591    * @param jprovider
3592    * @param pdbId
3593    * @return
3594    */
3595   String loadPDBFile(jarInputStreamProvider jprovider, String pdbId,
3596           String origFile)
3597   {
3598     if (alreadyLoadedPDB.containsKey(pdbId))
3599     {
3600       return alreadyLoadedPDB.get(pdbId).toString();
3601     }
3602
3603     String tempFile = copyJarEntry(jprovider, pdbId, "jalview_pdb",
3604             origFile);
3605     if (tempFile != null)
3606     {
3607       alreadyLoadedPDB.put(pdbId, tempFile);
3608     }
3609     return tempFile;
3610   }
3611
3612   /**
3613    * Copies the jar entry of given name to a new temporary file and returns the
3614    * path to the file, or null if the entry is not found.
3615    * 
3616    * @param jprovider
3617    * @param jarEntryName
3618    * @param prefix
3619    *          a prefix for the temporary file name, must be at least three
3620    *          characters long
3621    * @param suffixModel
3622    *          null or original file - so new file can be given the same suffix
3623    *          as the old one
3624    * @return
3625    */
3626   protected String copyJarEntry(jarInputStreamProvider jprovider,
3627           String jarEntryName, String prefix, String suffixModel)
3628   {
3629     String suffix = ".tmp";
3630     if (suffixModel == null)
3631     {
3632       suffixModel = jarEntryName;
3633     }
3634     int sfpos = suffixModel.lastIndexOf(".");
3635     if (sfpos > -1 && sfpos < (suffixModel.length() - 1))
3636     {
3637       suffix = "." + suffixModel.substring(sfpos + 1);
3638     }
3639
3640     try (JarInputStream jin = jprovider.getJarInputStream())
3641     {
3642       JarEntry entry = null;
3643       do
3644       {
3645         entry = jin.getNextJarEntry();
3646       } while (entry != null && !entry.getName().equals(jarEntryName));
3647
3648       if (entry != null)
3649       {
3650         // in = new BufferedReader(new InputStreamReader(jin, UTF_8));
3651         File outFile = File.createTempFile(prefix, suffix);
3652         outFile.deleteOnExit();
3653         try (OutputStream os = new FileOutputStream(outFile))
3654         {
3655           copyAll(jin, os);
3656         }
3657         String t = outFile.getAbsolutePath();
3658         return t;
3659       }
3660       else
3661       {
3662         Console.warn(
3663                 "Couldn't find entry in Jalview Jar for " + jarEntryName);
3664       }
3665     } catch (Exception ex)
3666     {
3667       ex.printStackTrace();
3668     }
3669
3670     return null;
3671   }
3672
3673   private class JvAnnotRow
3674   {
3675     public JvAnnotRow(int i, AlignmentAnnotation jaa)
3676     {
3677       order = i;
3678       template = jaa;
3679     }
3680
3681     /**
3682      * persisted version of annotation row from which to take vis properties
3683      */
3684     public jalview.datamodel.AlignmentAnnotation template;
3685
3686     /**
3687      * original position of the annotation row in the alignment
3688      */
3689     public int order;
3690   }
3691
3692   /**
3693    * Load alignment frame from jalview XML DOM object
3694    * 
3695    * @param jalviewModel
3696    *          DOM
3697    * @param file
3698    *          filename source string
3699    * @param loadTreesAndStructures
3700    *          when false only create Viewport
3701    * @param jprovider
3702    *          data source provider
3703    * @return alignment frame created from view stored in DOM
3704    */
3705   AlignFrame loadFromObject(JalviewModel jalviewModel, String file,
3706           boolean loadTreesAndStructures, jarInputStreamProvider jprovider)
3707   {
3708     SequenceSet vamsasSet = jalviewModel.getVamsasModel().getSequenceSet()
3709             .get(0);
3710     List<Sequence> vamsasSeqs = vamsasSet.getSequence();
3711
3712     // JalviewModelSequence jms = object.getJalviewModelSequence();
3713
3714     // Viewport view = (jms.getViewportCount() > 0) ? jms.getViewport(0)
3715     // : null;
3716     Viewport view = (jalviewModel.getViewport().size() > 0)
3717             ? jalviewModel.getViewport().get(0)
3718             : null;
3719
3720     // ////////////////////////////////
3721     // INITIALISE ALIGNMENT SEQUENCESETID AND VIEWID
3722     //
3723     //
3724     // If we just load in the same jar file again, the sequenceSetId
3725     // will be the same, and we end up with multiple references
3726     // to the same sequenceSet. We must modify this id on load
3727     // so that each load of the file gives a unique id
3728
3729     /**
3730      * used to resolve correct alignment dataset for alignments with multiple
3731      * views
3732      */
3733     String uniqueSeqSetId = null;
3734     String viewId = null;
3735     if (view != null)
3736     {
3737       uniqueSeqSetId = view.getSequenceSetId() + uniqueSetSuffix;
3738       viewId = (view.getId() == null ? null
3739               : view.getId() + uniqueSetSuffix);
3740     }
3741
3742     // ////////////////////////////////
3743     // LOAD MATRICES (IF ANY)
3744
3745     if (vamsasSet.getMatrix() != null && vamsasSet.getMatrix().size() > 0)
3746     {
3747       importMatrixData(vamsasSet.getMatrix());
3748     }
3749
3750     // ////////////////////////////////
3751     // LOAD SEQUENCES
3752
3753     List<SequenceI> hiddenSeqs = null;
3754
3755     List<SequenceI> tmpseqs = new ArrayList<>();
3756
3757     boolean multipleView = false;
3758     SequenceI referenceseqForView = null;
3759     // JSeq[] jseqs = object.getJalviewModelSequence().getJSeq();
3760     List<JSeq> jseqs = jalviewModel.getJSeq();
3761     int vi = 0; // counter in vamsasSeq array
3762     for (int i = 0; i < jseqs.size(); i++)
3763     {
3764       JSeq jseq = jseqs.get(i);
3765       String seqId = jseq.getId();
3766
3767       SequenceI tmpSeq = seqRefIds.get(seqId);
3768       if (tmpSeq != null)
3769       {
3770         if (!incompleteSeqs.containsKey(seqId))
3771         {
3772           // may not need this check, but keep it for at least 2.9,1 release
3773           if (tmpSeq.getStart() != jseq.getStart()
3774                   || tmpSeq.getEnd() != jseq.getEnd())
3775           {
3776             jalview.bin.Console.errPrintln(String.format(
3777                     "Warning JAL-2154 regression: updating start/end for sequence %s from %d/%d to %d/%d",
3778                     tmpSeq.getName(), tmpSeq.getStart(), tmpSeq.getEnd(),
3779                     jseq.getStart(), jseq.getEnd()));
3780           }
3781         }
3782         else
3783         {
3784           incompleteSeqs.remove(seqId);
3785         }
3786         if (vamsasSeqs.size() > vi
3787                 && vamsasSeqs.get(vi).getId().equals(seqId))
3788         {
3789           // most likely we are reading a dataset XML document so
3790           // update from vamsasSeq section of XML for this sequence
3791           tmpSeq.setName(vamsasSeqs.get(vi).getName());
3792           tmpSeq.setDescription(vamsasSeqs.get(vi).getDescription());
3793           tmpSeq.setSequence(vamsasSeqs.get(vi).getSequence());
3794           vi++;
3795         }
3796         else
3797         {
3798           // reading multiple views, so vamsasSeq set is a subset of JSeq
3799           multipleView = true;
3800         }
3801         tmpSeq.setStart(jseq.getStart());
3802         tmpSeq.setEnd(jseq.getEnd());
3803         tmpseqs.add(tmpSeq);
3804       }
3805       else
3806       {
3807         Sequence vamsasSeq = vamsasSeqs.get(vi);
3808         tmpSeq = new jalview.datamodel.Sequence(vamsasSeq.getName(),
3809                 vamsasSeq.getSequence());
3810         tmpSeq.setDescription(vamsasSeq.getDescription());
3811         tmpSeq.setStart(jseq.getStart());
3812         tmpSeq.setEnd(jseq.getEnd());
3813         tmpSeq.setVamsasId(uniqueSetSuffix + seqId);
3814         seqRefIds.put(vamsasSeq.getId(), tmpSeq);
3815         tmpseqs.add(tmpSeq);
3816         vi++;
3817       }
3818
3819       if (safeBoolean(jseq.isViewreference()))
3820       {
3821         referenceseqForView = tmpseqs.get(tmpseqs.size() - 1);
3822       }
3823
3824       if (jseq.isHidden() != null && jseq.isHidden().booleanValue())
3825       {
3826         if (hiddenSeqs == null)
3827         {
3828           hiddenSeqs = new ArrayList<>();
3829         }
3830
3831         hiddenSeqs.add(tmpSeq);
3832       }
3833     }
3834
3835     // /
3836     // Create the alignment object from the sequence set
3837     // ///////////////////////////////
3838     SequenceI[] orderedSeqs = tmpseqs
3839             .toArray(new SequenceI[tmpseqs.size()]);
3840
3841     AlignmentI al = null;
3842     // so we must create or recover the dataset alignment before going further
3843     // ///////////////////////////////
3844     if (vamsasSet.getDatasetId() == null || vamsasSet.getDatasetId() == "")
3845     {
3846       // older jalview projects do not have a dataset - so creat alignment and
3847       // dataset
3848       al = new Alignment(orderedSeqs);
3849       al.setDataset(null);
3850     }
3851     else
3852     {
3853       boolean isdsal = jalviewModel.getViewport().isEmpty();
3854       if (isdsal)
3855       {
3856         // we are importing a dataset record, so
3857         // recover reference to an alignment already materialsed as dataset
3858         al = getDatasetFor(vamsasSet.getDatasetId());
3859       }
3860       if (al == null)
3861       {
3862         // materialse the alignment
3863         al = new Alignment(orderedSeqs);
3864       }
3865       if (isdsal)
3866       {
3867         addDatasetRef(vamsasSet.getDatasetId(), al);
3868       }
3869
3870       // finally, verify all data in vamsasSet is actually present in al
3871       // passing on flag indicating if it is actually a stored dataset
3872       recoverDatasetFor(vamsasSet, al, isdsal, uniqueSeqSetId);
3873     }
3874
3875     if (referenceseqForView != null)
3876     {
3877       al.setSeqrep(referenceseqForView);
3878     }
3879     // / Add the alignment properties
3880     for (int i = 0; i < vamsasSet.getSequenceSetProperties().size(); i++)
3881     {
3882       SequenceSetProperties ssp = vamsasSet.getSequenceSetProperties()
3883               .get(i);
3884       al.setProperty(ssp.getKey(), ssp.getValue());
3885     }
3886
3887     // ///////////////////////////////
3888
3889     Hashtable pdbloaded = new Hashtable(); // TODO nothing writes to this??
3890     if (!multipleView)
3891     {
3892       // load sequence features, database references and any associated PDB
3893       // structures for the alignment
3894       //
3895       // prior to 2.10, this part would only be executed the first time a
3896       // sequence was encountered, but not afterwards.
3897       // now, for 2.10 projects, this is also done if the xml doc includes
3898       // dataset sequences not actually present in any particular view.
3899       //
3900       for (int i = 0; i < vamsasSeqs.size(); i++)
3901       {
3902         JSeq jseq = jseqs.get(i);
3903         if (jseq.getFeatures().size() > 0)
3904         {
3905           List<Feature> features = jseq.getFeatures();
3906           for (int f = 0; f < features.size(); f++)
3907           {
3908             Feature feat = features.get(f);
3909             SequenceFeature sf = new SequenceFeature(feat.getType(),
3910                     feat.getDescription(), feat.getBegin(), feat.getEnd(),
3911                     safeFloat(feat.getScore()), feat.getFeatureGroup());
3912             sf.setStatus(feat.getStatus());
3913
3914             /*
3915              * load any feature attributes - include map-valued attributes
3916              */
3917             Map<String, Map<String, String>> mapAttributes = new HashMap<>();
3918             for (int od = 0; od < feat.getOtherData().size(); od++)
3919             {
3920               OtherData keyValue = feat.getOtherData().get(od);
3921               String attributeName = keyValue.getKey();
3922               String attributeValue = keyValue.getValue();
3923               if (attributeName.startsWith("LINK"))
3924               {
3925                 sf.addLink(attributeValue);
3926               }
3927               else
3928               {
3929                 String subAttribute = keyValue.getKey2();
3930                 if (subAttribute == null)
3931                 {
3932                   // simple string-valued attribute
3933                   sf.setValue(attributeName, attributeValue);
3934                 }
3935                 else
3936                 {
3937                   // attribute 'key' has sub-attribute 'key2'
3938                   if (!mapAttributes.containsKey(attributeName))
3939                   {
3940                     mapAttributes.put(attributeName, new HashMap<>());
3941                   }
3942                   mapAttributes.get(attributeName).put(subAttribute,
3943                           attributeValue);
3944                 }
3945               }
3946             }
3947             for (Entry<String, Map<String, String>> mapAttribute : mapAttributes
3948                     .entrySet())
3949             {
3950               sf.setValue(mapAttribute.getKey(), mapAttribute.getValue());
3951             }
3952
3953             // adds feature to datasequence's feature set (since Jalview 2.10)
3954             al.getSequenceAt(i).addSequenceFeature(sf);
3955           }
3956         }
3957         if (vamsasSeqs.get(i).getDBRef().size() > 0)
3958         {
3959           // adds dbrefs to datasequence's set (since Jalview 2.10)
3960           addDBRefs(
3961                   al.getSequenceAt(i).getDatasetSequence() == null
3962                           ? al.getSequenceAt(i)
3963                           : al.getSequenceAt(i).getDatasetSequence(),
3964                   vamsasSeqs.get(i));
3965         }
3966         if (jseq.getPdbids().size() > 0)
3967         {
3968           List<Pdbids> ids = jseq.getPdbids();
3969           for (int p = 0; p < ids.size(); p++)
3970           {
3971             Pdbids pdbid = ids.get(p);
3972             jalview.datamodel.PDBEntry entry = new jalview.datamodel.PDBEntry();
3973             entry.setId(pdbid.getId());
3974             if (pdbid.getType() != null)
3975             {
3976               if (PDBEntry.Type.getType(pdbid.getType()) != null)
3977               {
3978                 entry.setType(PDBEntry.Type.getType(pdbid.getType()));
3979               }
3980               else
3981               {
3982                 entry.setType(PDBEntry.Type.FILE);
3983               }
3984             }
3985             // jprovider is null when executing 'New View'
3986             if (pdbid.getFile() != null && jprovider != null)
3987             {
3988               if (!pdbloaded.containsKey(pdbid.getFile()))
3989               {
3990                 entry.setFile(loadPDBFile(jprovider, pdbid.getId(),
3991                         pdbid.getFile()));
3992               }
3993               else
3994               {
3995                 entry.setFile(pdbloaded.get(pdbid.getId()).toString());
3996               }
3997             }
3998             /*
3999             if (pdbid.getPdbentryItem() != null)
4000             {
4001               for (PdbentryItem item : pdbid.getPdbentryItem())
4002               {
4003                 for (Property pr : item.getProperty())
4004                 {
4005                   entry.setProperty(pr.getName(), pr.getValue());
4006                 }
4007               }
4008             }
4009             */
4010             for (Property prop : pdbid.getProperty())
4011             {
4012               entry.setProperty(prop.getName(), prop.getValue());
4013             }
4014             StructureSelectionManager
4015                     .getStructureSelectionManager(Desktop.instance)
4016                     .registerPDBEntry(entry);
4017             // adds PDBEntry to datasequence's set (since Jalview 2.10)
4018             if (al.getSequenceAt(i).getDatasetSequence() != null)
4019             {
4020               al.getSequenceAt(i).getDatasetSequence().addPDBId(entry);
4021             }
4022             else
4023             {
4024               al.getSequenceAt(i).addPDBId(entry);
4025             }
4026           }
4027         }
4028       }
4029     } // end !multipleview
4030
4031     // ///////////////////////////////
4032     // LOAD SEQUENCE MAPPINGS
4033
4034     if (vamsasSet.getAlcodonFrame().size() > 0)
4035     {
4036       // TODO Potentially this should only be done once for all views of an
4037       // alignment
4038       List<AlcodonFrame> alc = vamsasSet.getAlcodonFrame();
4039       for (int i = 0; i < alc.size(); i++)
4040       {
4041         AlignedCodonFrame cf = new AlignedCodonFrame();
4042         if (alc.get(i).getAlcodMap().size() > 0)
4043         {
4044           List<AlcodMap> maps = alc.get(i).getAlcodMap();
4045           for (int m = 0; m < maps.size(); m++)
4046           {
4047             AlcodMap map = maps.get(m);
4048             SequenceI dnaseq = seqRefIds.get(map.getDnasq());
4049             // Load Mapping
4050             jalview.datamodel.Mapping mapping = null;
4051             // attach to dna sequence reference.
4052             if (map.getMapping() != null)
4053             {
4054               mapping = addMapping(map.getMapping());
4055               if (dnaseq != null && mapping.getTo() != null)
4056               {
4057                 cf.addMap(dnaseq, mapping.getTo(), mapping.getMap());
4058               }
4059               else
4060               {
4061                 // defer to later
4062                 frefedSequence
4063                         .add(newAlcodMapRef(map.getDnasq(), cf, mapping));
4064               }
4065             }
4066           }
4067           al.addCodonFrame(cf);
4068         }
4069       }
4070     }
4071
4072     // ////////////////////////////////
4073     // LOAD ANNOTATIONS
4074     List<JvAnnotRow> autoAlan = new ArrayList<>();
4075
4076     /*
4077      * store any annotations which forward reference a group's ID
4078      */
4079     Map<String, List<AlignmentAnnotation>> groupAnnotRefs = new Hashtable<>();
4080
4081     if (vamsasSet.getAnnotation().size()/*Count()*/ > 0)
4082     {
4083       List<Annotation> an = vamsasSet.getAnnotation();
4084
4085       for (int i = 0; i < an.size(); i++)
4086       {
4087         Annotation annotation = an.get(i);
4088
4089         /**
4090          * test if annotation is automatically calculated for this view only
4091          */
4092         boolean autoForView = false;
4093         if (annotation.getLabel().equals("Quality")
4094                 || annotation.getLabel().equals("Conservation")
4095                 || annotation.getLabel().equals("Consensus"))
4096         {
4097           // Kludge for pre 2.5 projects which lacked the autocalculated flag
4098           autoForView = true;
4099           // JAXB has no has() test; schema defaults value to false
4100           // if (!annotation.hasAutoCalculated())
4101           // {
4102           // annotation.setAutoCalculated(true);
4103           // }
4104         }
4105         if (autoForView || annotation.isAutoCalculated())
4106         {
4107           // remove ID - we don't recover annotation from other views for
4108           // view-specific annotation
4109           annotation.setId(null);
4110         }
4111
4112         // set visibility for other annotation in this view
4113         String annotationId = annotation.getId();
4114         if (annotationId != null && annotationIds.containsKey(annotationId))
4115         {
4116           AlignmentAnnotation jda = annotationIds.get(annotationId);
4117           // in principle Visible should always be true for annotation displayed
4118           // in multiple views
4119           if (annotation.isVisible() != null)
4120           {
4121             jda.visible = annotation.isVisible();
4122           }
4123
4124           al.addAnnotation(jda);
4125
4126           continue;
4127         }
4128         // Construct new annotation from model.
4129         List<AnnotationElement> ae = annotation.getAnnotationElement();
4130         jalview.datamodel.Annotation[] anot = null;
4131         java.awt.Color firstColour = null;
4132         int anpos;
4133         if (!annotation.isScoreOnly())
4134         {
4135           anot = new jalview.datamodel.Annotation[al.getWidth()];
4136           for (int aa = 0; aa < ae.size() && aa < anot.length; aa++)
4137           {
4138             AnnotationElement annElement = ae.get(aa);
4139             anpos = annElement.getPosition();
4140
4141             if (anpos >= anot.length)
4142             {
4143               continue;
4144             }
4145
4146             float value = safeFloat(annElement.getValue());
4147             anot[anpos] = new jalview.datamodel.Annotation(
4148                     annElement.getDisplayCharacter(),
4149                     annElement.getDescription(),
4150                     (annElement.getSecondaryStructure() == null
4151                             || annElement.getSecondaryStructure()
4152                                     .length() == 0)
4153                                             ? ' '
4154                                             : annElement
4155                                                     .getSecondaryStructure()
4156                                                     .charAt(0),
4157                     value);
4158             anot[anpos].colour = new Color(safeInt(annElement.getColour()));
4159             if (firstColour == null)
4160             {
4161               firstColour = anot[anpos].colour;
4162             }
4163           }
4164         }
4165         jalview.datamodel.AlignmentAnnotation jaa = null;
4166
4167         if (annotation.isGraph())
4168         {
4169           float llim = 0, hlim = 0;
4170           // if (autoForView || an[i].isAutoCalculated()) {
4171           // hlim=11f;
4172           // }
4173           jaa = new jalview.datamodel.AlignmentAnnotation(
4174                   annotation.getLabel(), annotation.getDescription(), anot,
4175                   llim, hlim, safeInt(annotation.getGraphType()));
4176
4177           jaa.graphGroup = safeInt(annotation.getGraphGroup());
4178           jaa._linecolour = firstColour;
4179           if (annotation.getThresholdLine() != null)
4180           {
4181             jaa.setThreshold(new jalview.datamodel.GraphLine(
4182                     safeFloat(annotation.getThresholdLine().getValue()),
4183                     annotation.getThresholdLine().getLabel(),
4184                     new java.awt.Color(safeInt(
4185                             annotation.getThresholdLine().getColour()))));
4186           }
4187           if (autoForView || annotation.isAutoCalculated())
4188           {
4189             // Hardwire the symbol display line to ensure that labels for
4190             // histograms are displayed
4191             jaa.hasText = true;
4192           }
4193         }
4194         else
4195         {
4196           jaa = new jalview.datamodel.AlignmentAnnotation(
4197                   annotation.getLabel(), annotation.getDescription(), anot);
4198           jaa._linecolour = firstColour;
4199         }
4200         // register new annotation
4201         if (annotation.getId() != null)
4202         {
4203           annotationIds.put(annotation.getId(), jaa);
4204           jaa.annotationId = annotation.getId();
4205         }
4206         // recover sequence association
4207         String sequenceRef = annotation.getSequenceRef();
4208         if (sequenceRef != null)
4209         {
4210           // from 2.9 sequenceRef is to sequence id (JAL-1781)
4211           SequenceI sequence = seqRefIds.get(sequenceRef);
4212           if (sequence == null)
4213           {
4214             // in pre-2.9 projects sequence ref is to sequence name
4215             sequence = al.findName(sequenceRef);
4216           }
4217           if (sequence != null)
4218           {
4219             jaa.createSequenceMapping(sequence, 1, true);
4220             sequence.addAlignmentAnnotation(jaa);
4221           }
4222         }
4223         // and make a note of any group association
4224         if (annotation.getGroupRef() != null
4225                 && annotation.getGroupRef().length() > 0)
4226         {
4227           List<jalview.datamodel.AlignmentAnnotation> aal = groupAnnotRefs
4228                   .get(annotation.getGroupRef());
4229           if (aal == null)
4230           {
4231             aal = new ArrayList<>();
4232             groupAnnotRefs.put(annotation.getGroupRef(), aal);
4233           }
4234           aal.add(jaa);
4235         }
4236
4237         if (annotation.getScore() != null)
4238         {
4239           jaa.setScore(annotation.getScore().doubleValue());
4240         }
4241         if (annotation.isVisible() != null)
4242         {
4243           jaa.visible = annotation.isVisible().booleanValue();
4244         }
4245
4246         if (annotation.isCentreColLabels() != null)
4247         {
4248           jaa.centreColLabels = annotation.isCentreColLabels()
4249                   .booleanValue();
4250         }
4251
4252         if (annotation.isScaleColLabels() != null)
4253         {
4254           jaa.scaleColLabel = annotation.isScaleColLabels().booleanValue();
4255         }
4256         if (annotation.isAutoCalculated())
4257         {
4258           // newer files have an 'autoCalculated' flag and store calculation
4259           // state in viewport properties
4260           jaa.autoCalculated = true; // means annotation will be marked for
4261           // update at end of load.
4262         }
4263         if (annotation.getGraphHeight() != null)
4264         {
4265           jaa.graphHeight = annotation.getGraphHeight().intValue();
4266         }
4267         jaa.belowAlignment = annotation.isBelowAlignment();
4268         jaa.setCalcId(annotation.getCalcId());
4269         if (annotation.getProperty().size() > 0)
4270         {
4271           for (jalview.xml.binding.jalview.Property prop : annotation
4272                   .getProperty())
4273           {
4274             jaa.setProperty(prop.getName(), prop.getValue());
4275           }
4276         }
4277         if (jaa.graph == AlignmentAnnotation.CONTACT_MAP)
4278         {
4279           if (annotation.getContactmatrix() != null
4280                   && annotation.getContactmatrix().size() > 0)
4281           {
4282             for (MapOnAMatrixType xmlmat : annotation.getContactmatrix())
4283             {
4284               restoreMatrixFor(jaa.sequenceRef, jaa, xmlmat);
4285             }
4286           }
4287         }
4288
4289         if (jaa.autoCalculated)
4290         {
4291           autoAlan.add(new JvAnnotRow(i, jaa));
4292         }
4293         else
4294         // if (!autoForView)
4295         {
4296           // add autocalculated group annotation and any user created annotation
4297           // for the view
4298           al.addAnnotation(jaa);
4299         }
4300       }
4301     }
4302     // ///////////////////////
4303     // LOAD GROUPS
4304     // Create alignment markup and styles for this view
4305     if (jalviewModel.getJGroup().size() > 0)
4306     {
4307       List<JGroup> groups = jalviewModel.getJGroup();
4308       boolean addAnnotSchemeGroup = false;
4309       for (int i = 0; i < groups.size(); i++)
4310       {
4311         JGroup jGroup = groups.get(i);
4312         ColourSchemeI cs = null;
4313         if (jGroup.getColour() != null)
4314         {
4315           if (jGroup.getColour().startsWith("ucs"))
4316           {
4317             cs = getUserColourScheme(jalviewModel, jGroup.getColour());
4318           }
4319           else if (jGroup.getColour().equals("AnnotationColourGradient")
4320                   && jGroup.getAnnotationColours() != null)
4321           {
4322             addAnnotSchemeGroup = true;
4323           }
4324           else
4325           {
4326             cs = ColourSchemeProperty.getColourScheme(null, al,
4327                     jGroup.getColour());
4328           }
4329         }
4330         int pidThreshold = safeInt(jGroup.getPidThreshold());
4331
4332         Vector<SequenceI> seqs = new Vector<>();
4333
4334         for (int s = 0; s < jGroup.getSeq().size(); s++)
4335         {
4336           String seqId = jGroup.getSeq().get(s);
4337           SequenceI ts = seqRefIds.get(seqId);
4338
4339           if (ts != null)
4340           {
4341             seqs.addElement(ts);
4342           }
4343         }
4344
4345         if (seqs.size() < 1)
4346         {
4347           continue;
4348         }
4349
4350         SequenceGroup sg = new SequenceGroup(seqs, jGroup.getName(), cs,
4351                 safeBoolean(jGroup.isDisplayBoxes()),
4352                 safeBoolean(jGroup.isDisplayText()),
4353                 safeBoolean(jGroup.isColourText()),
4354                 safeInt(jGroup.getStart()), safeInt(jGroup.getEnd()));
4355         sg.getGroupColourScheme().setThreshold(pidThreshold, true);
4356         sg.getGroupColourScheme()
4357                 .setConservationInc(safeInt(jGroup.getConsThreshold()));
4358         sg.setOutlineColour(new Color(safeInt(jGroup.getOutlineColour())));
4359
4360         sg.textColour = new Color(safeInt(jGroup.getTextCol1()));
4361         sg.textColour2 = new Color(safeInt(jGroup.getTextCol2()));
4362         sg.setShowNonconserved(safeBoolean(jGroup.isShowUnconserved()));
4363         sg.thresholdTextColour = safeInt(jGroup.getTextColThreshold());
4364         // attributes with a default in the schema are never null
4365         sg.setShowConsensusHistogram(jGroup.isShowConsensusHistogram());
4366         sg.setshowSequenceLogo(jGroup.isShowSequenceLogo());
4367         sg.setNormaliseSequenceLogo(jGroup.isNormaliseSequenceLogo());
4368         sg.setIgnoreGapsConsensus(jGroup.isIgnoreGapsinConsensus());
4369         if (jGroup.getConsThreshold() != null
4370                 && jGroup.getConsThreshold().intValue() != 0)
4371         {
4372           Conservation c = new Conservation("All", sg.getSequences(null), 0,
4373                   sg.getWidth() - 1);
4374           c.calculate();
4375           c.verdict(false, 25);
4376           sg.cs.setConservation(c);
4377         }
4378
4379         if (jGroup.getId() != null && groupAnnotRefs.size() > 0)
4380         {
4381           // re-instate unique group/annotation row reference
4382           List<AlignmentAnnotation> jaal = groupAnnotRefs
4383                   .get(jGroup.getId());
4384           if (jaal != null)
4385           {
4386             for (AlignmentAnnotation jaa : jaal)
4387             {
4388               jaa.groupRef = sg;
4389               if (jaa.autoCalculated)
4390               {
4391                 // match up and try to set group autocalc alignment row for this
4392                 // annotation
4393                 if (jaa.label.startsWith("Consensus for "))
4394                 {
4395                   sg.setConsensus(jaa);
4396                 }
4397                 // match up and try to set group autocalc alignment row for this
4398                 // annotation
4399                 if (jaa.label.startsWith("Conservation for "))
4400                 {
4401                   sg.setConservationRow(jaa);
4402                 }
4403               }
4404             }
4405           }
4406         }
4407         al.addGroup(sg);
4408         if (addAnnotSchemeGroup)
4409         {
4410           // reconstruct the annotation colourscheme
4411           sg.setColourScheme(
4412                   constructAnnotationColour(jGroup.getAnnotationColours(),
4413                           null, al, jalviewModel, false));
4414         }
4415       }
4416     }
4417     if (view == null)
4418     {
4419       // only dataset in this model, so just return.
4420       return null;
4421     }
4422     // ///////////////////////////////
4423     // LOAD VIEWPORT
4424
4425     AlignFrame af = null;
4426     AlignViewport av = null;
4427     // now check to see if we really need to create a new viewport.
4428     if (multipleView && viewportsAdded.size() == 0)
4429     {
4430       // We recovered an alignment for which a viewport already exists.
4431       // TODO: fix up any settings necessary for overlaying stored state onto
4432       // state recovered from another document. (may not be necessary).
4433       // we may need a binding from a viewport in memory to one recovered from
4434       // XML.
4435       // and then recover its containing af to allow the settings to be applied.
4436       // TODO: fix for vamsas demo
4437       jalview.bin.Console.errPrintln(
4438               "About to recover a viewport for existing alignment: Sequence set ID is "
4439                       + uniqueSeqSetId);
4440       Object seqsetobj = retrieveExistingObj(uniqueSeqSetId);
4441       if (seqsetobj != null)
4442       {
4443         if (seqsetobj instanceof String)
4444         {
4445           uniqueSeqSetId = (String) seqsetobj;
4446           jalview.bin.Console.errPrintln(
4447                   "Recovered extant sequence set ID mapping for ID : New Sequence set ID is "
4448                           + uniqueSeqSetId);
4449         }
4450         else
4451         {
4452           jalview.bin.Console.errPrintln(
4453                   "Warning : Collision between sequence set ID string and existing jalview object mapping.");
4454         }
4455
4456       }
4457     }
4458     /**
4459      * indicate that annotation colours are applied across all groups (pre
4460      * Jalview 2.8.1 behaviour)
4461      */
4462     boolean doGroupAnnColour = Jalview2XML.isVersionStringLaterThan("2.8.1",
4463             jalviewModel.getVersion());
4464
4465     AlignmentPanel ap = null;
4466     boolean isnewview = true;
4467     if (viewId != null)
4468     {
4469       // Check to see if this alignment already has a view id == viewId
4470       jalview.gui.AlignmentPanel views[] = Desktop
4471               .getAlignmentPanels(uniqueSeqSetId);
4472       if (views != null && views.length > 0)
4473       {
4474         for (int v = 0; v < views.length; v++)
4475         {
4476           if (views[v].av.getViewId().equalsIgnoreCase(viewId))
4477           {
4478             // recover the existing alignpanel, alignframe, viewport
4479             af = views[v].alignFrame;
4480             av = views[v].av;
4481             ap = views[v];
4482             // TODO: could even skip resetting view settings if we don't want to
4483             // change the local settings from other jalview processes
4484             isnewview = false;
4485           }
4486         }
4487       }
4488     }
4489
4490     if (isnewview)
4491     {
4492       af = loadViewport(file, jseqs, hiddenSeqs, al, jalviewModel, view,
4493               uniqueSeqSetId, viewId, autoAlan);
4494       av = af.getViewport();
4495       ap = af.alignPanel;
4496     }
4497
4498     /*
4499      * Load any trees, PDB structures and viewers, Overview
4500      * 
4501      * Not done if flag is false (when this method is used for New View)
4502      */
4503     if (loadTreesAndStructures)
4504     {
4505       loadTrees(jalviewModel, view, af, av, ap);
4506       loadPCAViewers(jalviewModel, ap);
4507       loadPaSiMapViewers(jalviewModel, ap);
4508       loadPDBStructures(jprovider, jseqs, af, ap);
4509       loadRnaViewers(jprovider, jseqs, ap);
4510       loadOverview(view, jalviewModel.getVersion(), af);
4511     }
4512     // and finally return.
4513     return af;
4514   }
4515
4516   private void importMatrixData(List<MatrixType> xmlmatrices)
4517   {
4518     for (MatrixType xmlmat : xmlmatrices)
4519     {
4520       if (!PAEContactMatrix.PAEMATRIX.equals(xmlmat.getType()))
4521       {
4522         Console.error("Ignoring matrix '" + xmlmat.getId() + "' of type '"
4523                 + xmlmat.getType());
4524         continue;
4525       }
4526
4527       if (!xmlmat.getRows().equals(xmlmat.getCols()))
4528       {
4529         Console.error("Can't handle non square matrices");
4530         continue;
4531       }
4532
4533       float[][] elements = ContactMatrix.fromFloatStringToContacts(
4534               xmlmat.getElements(), xmlmat.getCols().intValue(),
4535               xmlmat.getRows().intValue());
4536
4537       List<BitSet> newgroups = new ArrayList<BitSet>();
4538       if (xmlmat.getGroups().size() > 0)
4539       {
4540         for (String sgroup : xmlmat.getGroups())
4541         {
4542           newgroups.add(deStringifyBitset(sgroup));
4543         }
4544       }
4545       String nwk = xmlmat.getNewick().size() > 0 ? xmlmat.getNewick().get(0)
4546               : null;
4547       if (xmlmat.getNewick().size() > 1)
4548       {
4549         Console.log
4550                 .info("Ignoring additional clusterings for contact matrix");
4551       }
4552       String treeMethod = xmlmat.getTreeMethod();
4553       double thresh = xmlmat.getCutHeight() != null ? xmlmat.getCutHeight()
4554               : 0;
4555       GroupSet grpset = new GroupSet();
4556       grpset.restoreGroups(newgroups, treeMethod, nwk, thresh);
4557
4558       FloatContactMatrix newcm = new FloatContactMatrix(elements, grpset);
4559       contactMatrixRefs.put(xmlmat.getId(), newcm);
4560       Console.trace("Restored base contact matrix " + xmlmat.getId());
4561     }
4562   }
4563
4564   private void restoreMatrixFor(SequenceI sequenceRef,
4565           AlignmentAnnotation jaa, MapOnAMatrixType xmlmatmapping)
4566   {
4567     // restore mapping data to matrix data
4568     jalview.util.MapList mapping = null;
4569     if (xmlmatmapping.getMapping() != null)
4570     {
4571       MapListType m = xmlmatmapping.getMapping();
4572       // Mapping m = dr.getMapping();
4573       int fr[] = new int[m.getMapListFrom().size() * 2];
4574       Iterator<MapListFrom> from = m.getMapListFrom().iterator();// enumerateMapListFrom();
4575       for (int _i = 0; from.hasNext(); _i += 2)
4576       {
4577         MapListFrom mf = from.next();
4578         fr[_i] = mf.getStart();
4579         fr[_i + 1] = mf.getEnd();
4580       }
4581       int fto[] = new int[m.getMapListTo().size() * 2];
4582       Iterator<MapListTo> to = m.getMapListTo().iterator();// enumerateMapListTo();
4583       for (int _i = 0; to.hasNext(); _i += 2)
4584       {
4585         MapListTo mf = to.next();
4586         fto[_i] = mf.getStart();
4587         fto[_i + 1] = mf.getEnd();
4588       }
4589
4590       mapping = new jalview.util.MapList(fr, fto,
4591               m.getMapFromUnit().intValue(), m.getMapToUnit().intValue());
4592     }
4593
4594     // locate matrix data in project XML and import
4595     ContactMatrixI cm = contactMatrixRefs.get(xmlmatmapping.getMatrix());
4596     if (cm == null)
4597     {
4598       frefedSequence
4599               .add(newMatrixFref(xmlmatmapping.getMatrix(), mapping, jaa));
4600     }
4601     else
4602     {
4603       // create the PAEMatrix now
4604       PAEContactMatrix newpae = new PAEContactMatrix(jaa.sequenceRef,
4605               mapping, cm);
4606
4607       jaa.sequenceRef.addContactListFor(jaa, newpae);
4608     }
4609
4610     return;
4611   }
4612
4613   /**
4614    * Load Overview window, restoring colours, 'show hidden regions' flag, title
4615    * and geometry as saved
4616    * 
4617    * @param view
4618    * @param af
4619    */
4620   protected void loadOverview(Viewport view, String version, AlignFrame af)
4621   {
4622     if (!isVersionStringLaterThan("2.11.3", version)
4623             && view.getOverview() == null)
4624     {
4625       return;
4626     }
4627     /*
4628      * first close any Overview that was opened automatically
4629      * (if so configured in Preferences) so that the view is
4630      * restored in the same state as saved
4631      */
4632     af.alignPanel.closeOverviewPanel();
4633
4634     Overview overview = view.getOverview();
4635     if (overview != null)
4636     {
4637       OverviewPanel overviewPanel = af
4638               .openOverviewPanel(overview.isShowHidden());
4639       overviewPanel.setTitle(overview.getTitle());
4640       overviewPanel.setFrameBounds(overview.getXpos(), overview.getYpos(),
4641               overview.getWidth(), overview.getHeight());
4642       Color gap = new Color(overview.getGapColour());
4643       Color residue = new Color(overview.getResidueColour());
4644       Color hidden = new Color(overview.getHiddenColour());
4645       overviewPanel.getCanvas().setColours(gap, residue, hidden);
4646     }
4647   }
4648
4649   /**
4650    * Instantiate and link any saved RNA (Varna) viewers. The state of the Varna
4651    * panel is restored from separate jar entries, two (gapped and trimmed) per
4652    * sequence and secondary structure.
4653    * 
4654    * Currently each viewer shows just one sequence and structure (gapped and
4655    * trimmed), however this method is designed to support multiple sequences or
4656    * structures in viewers if wanted in future.
4657    * 
4658    * @param jprovider
4659    * @param jseqs
4660    * @param ap
4661    */
4662   private void loadRnaViewers(jarInputStreamProvider jprovider,
4663           List<JSeq> jseqs, AlignmentPanel ap)
4664   {
4665     /*
4666      * scan the sequences for references to viewers; create each one the first
4667      * time it is referenced, add Rna models to existing viewers
4668      */
4669     for (JSeq jseq : jseqs)
4670     {
4671       for (int i = 0; i < jseq.getRnaViewer().size(); i++)
4672       {
4673         RnaViewer viewer = jseq.getRnaViewer().get(i);
4674         AppVarna appVarna = findOrCreateVarnaViewer(viewer, uniqueSetSuffix,
4675                 ap);
4676
4677         for (int j = 0; j < viewer.getSecondaryStructure().size(); j++)
4678         {
4679           SecondaryStructure ss = viewer.getSecondaryStructure().get(j);
4680           SequenceI seq = seqRefIds.get(jseq.getId());
4681           AlignmentAnnotation ann = this.annotationIds
4682                   .get(ss.getAnnotationId());
4683
4684           /*
4685            * add the structure to the Varna display (with session state copied
4686            * from the jar to a temporary file)
4687            */
4688           boolean gapped = safeBoolean(ss.isGapped());
4689           String rnaTitle = ss.getTitle();
4690           String sessionState = ss.getViewerState();
4691           String tempStateFile = copyJarEntry(jprovider, sessionState,
4692                   "varna", null);
4693           RnaModel rna = new RnaModel(rnaTitle, ann, seq, null, gapped);
4694           appVarna.addModelSession(rna, rnaTitle, tempStateFile);
4695         }
4696         appVarna.setInitialSelection(safeInt(viewer.getSelectedRna()));
4697       }
4698     }
4699   }
4700
4701   /**
4702    * Locate and return an already instantiated matching AppVarna, or create one
4703    * if not found
4704    * 
4705    * @param viewer
4706    * @param viewIdSuffix
4707    * @param ap
4708    * @return
4709    */
4710   protected AppVarna findOrCreateVarnaViewer(RnaViewer viewer,
4711           String viewIdSuffix, AlignmentPanel ap)
4712   {
4713     /*
4714      * on each load a suffix is appended to the saved viewId, to avoid conflicts
4715      * if load is repeated
4716      */
4717     String postLoadId = viewer.getViewId() + viewIdSuffix;
4718     for (JInternalFrame frame : getAllFrames())
4719     {
4720       if (frame instanceof AppVarna)
4721       {
4722         AppVarna varna = (AppVarna) frame;
4723         if (postLoadId.equals(varna.getViewId()))
4724         {
4725           // this viewer is already instantiated
4726           // could in future here add ap as another 'parent' of the
4727           // AppVarna window; currently just 1-to-many
4728           return varna;
4729         }
4730       }
4731     }
4732
4733     /*
4734      * viewer not found - make it
4735      */
4736     RnaViewerModel model = new RnaViewerModel(postLoadId, viewer.getTitle(),
4737             safeInt(viewer.getXpos()), safeInt(viewer.getYpos()),
4738             safeInt(viewer.getWidth()), safeInt(viewer.getHeight()),
4739             safeInt(viewer.getDividerLocation()));
4740     AppVarna varna = new AppVarna(model, ap);
4741
4742     return varna;
4743   }
4744
4745   /**
4746    * Load any saved trees
4747    * 
4748    * @param jm
4749    * @param view
4750    * @param af
4751    * @param av
4752    * @param ap
4753    */
4754   protected void loadTrees(JalviewModel jm, Viewport view, AlignFrame af,
4755           AlignViewport av, AlignmentPanel ap)
4756   {
4757     // TODO result of automated refactoring - are all these parameters needed?
4758     try
4759     {
4760       for (int t = 0; t < jm.getTree().size(); t++)
4761       {
4762
4763         Tree tree = jm.getTree().get(t);
4764
4765         TreePanel tp = (TreePanel) retrieveExistingObj(tree.getId());
4766         if (tp == null)
4767         {
4768           if (tree.isColumnWise())
4769           {
4770             AlignmentAnnotation aa = annotationIds
4771                     .get(tree.getColumnReference());
4772             if (aa == null)
4773             {
4774               Console.warn(
4775                       "Null alignment annotation when restoring columnwise tree");
4776             }
4777             tp = af.showColumnWiseTree(new NewickFile(tree.getNewick()), aa,
4778                     tree.getTitle(), safeInt(tree.getWidth()),
4779                     safeInt(tree.getHeight()), safeInt(tree.getXpos()),
4780                     safeInt(tree.getYpos()));
4781
4782           }
4783           else
4784           {
4785             tp = af.showNewickTree(new NewickFile(tree.getNewick()),
4786                     tree.getTitle(), safeInt(tree.getWidth()),
4787                     safeInt(tree.getHeight()), safeInt(tree.getXpos()),
4788                     safeInt(tree.getYpos()));
4789           }
4790           if (tree.getId() != null)
4791           {
4792             // perhaps bind the tree id to something ?
4793           }
4794         }
4795         else
4796         {
4797           // update local tree attributes ?
4798           // TODO: should check if tp has been manipulated by user - if so its
4799           // settings shouldn't be modified
4800           tp.setTitle(tree.getTitle());
4801           tp.setBounds(new Rectangle(safeInt(tree.getXpos()),
4802                   safeInt(tree.getYpos()), safeInt(tree.getWidth()),
4803                   safeInt(tree.getHeight())));
4804           tp.setViewport(av); // af.viewport;
4805           // TODO: verify 'associate with all views' works still
4806           tp.getTreeCanvas().setViewport(av); // af.viewport;
4807           tp.getTreeCanvas().setAssociatedPanel(ap); // af.alignPanel;
4808         }
4809         tp.getTreeCanvas().setApplyToAllViews(tree.isLinkToAllViews());
4810         if (tp == null)
4811         {
4812           Console.warn(
4813                   "There was a problem recovering stored Newick tree: \n"
4814                           + tree.getNewick());
4815           continue;
4816         }
4817
4818         tp.fitToWindow.setState(safeBoolean(tree.isFitToWindow()));
4819         tp.fitToWindow_actionPerformed(null);
4820
4821         if (tree.getFontName() != null)
4822         {
4823           tp.setTreeFont(
4824                   new Font(tree.getFontName(), safeInt(tree.getFontStyle()),
4825                           safeInt(tree.getFontSize())));
4826         }
4827         else
4828         {
4829           tp.setTreeFont(
4830                   new Font(view.getFontName(), safeInt(view.getFontStyle()),
4831                           safeInt(view.getFontSize())));
4832         }
4833
4834         tp.showPlaceholders(safeBoolean(tree.isMarkUnlinked()));
4835         tp.showBootstrap(safeBoolean(tree.isShowBootstrap()));
4836         tp.showDistances(safeBoolean(tree.isShowDistances()));
4837
4838         tp.getTreeCanvas().setThreshold(safeFloat(tree.getThreshold()));
4839
4840         if (safeBoolean(tree.isCurrentTree()))
4841         {
4842           af.getViewport().setCurrentTree(tp.getTree());
4843         }
4844       }
4845
4846     } catch (Exception ex)
4847     {
4848       ex.printStackTrace();
4849     }
4850   }
4851
4852   /**
4853    * Load and link any saved structure viewers.
4854    * 
4855    * @param jprovider
4856    * @param jseqs
4857    * @param af
4858    * @param ap
4859    */
4860   protected void loadPDBStructures(jarInputStreamProvider jprovider,
4861           List<JSeq> jseqs, AlignFrame af, AlignmentPanel ap)
4862   {
4863     /*
4864      * Run through all PDB ids on the alignment, and collect mappings between
4865      * distinct view ids and all sequences referring to that view.
4866      */
4867     Map<String, StructureViewerModel> structureViewers = new LinkedHashMap<>();
4868
4869     for (int i = 0; i < jseqs.size(); i++)
4870     {
4871       JSeq jseq = jseqs.get(i);
4872       if (jseq.getPdbids().size() > 0)
4873       {
4874         List<Pdbids> ids = jseq.getPdbids();
4875         for (int p = 0; p < ids.size(); p++)
4876         {
4877           Pdbids pdbid = ids.get(p);
4878           final int structureStateCount = pdbid.getStructureState().size();
4879           for (int s = 0; s < structureStateCount; s++)
4880           {
4881             // check to see if we haven't already created this structure view
4882             final StructureState structureState = pdbid.getStructureState()
4883                     .get(s);
4884             String sviewid = (structureState.getViewId() == null) ? null
4885                     : structureState.getViewId() + uniqueSetSuffix;
4886             jalview.datamodel.PDBEntry jpdb = new jalview.datamodel.PDBEntry();
4887             // Originally : pdbid.getFile()
4888             // : TODO: verify external PDB file recovery still works in normal
4889             // jalview project load
4890             jpdb.setFile(
4891                     loadPDBFile(jprovider, pdbid.getId(), pdbid.getFile()));
4892             jpdb.setId(pdbid.getId());
4893
4894             int x = safeInt(structureState.getXpos());
4895             int y = safeInt(structureState.getYpos());
4896             int width = safeInt(structureState.getWidth());
4897             int height = safeInt(structureState.getHeight());
4898
4899             // Probably don't need to do this anymore...
4900             // Desktop.desktop.getComponentAt(x, y);
4901             // TODO: NOW: check that this recovers the PDB file correctly.
4902             String pdbFile = loadPDBFile(jprovider, pdbid.getId(),
4903                     pdbid.getFile());
4904             jalview.datamodel.SequenceI seq = seqRefIds
4905                     .get(jseq.getId() + "");
4906             if (sviewid == null)
4907             {
4908               sviewid = "_jalview_pre2_4_" + x + "," + y + "," + width + ","
4909                       + height;
4910             }
4911             if (!structureViewers.containsKey(sviewid))
4912             {
4913               String viewerType = structureState.getType();
4914               if (viewerType == null) // pre Jalview 2.9
4915               {
4916                 viewerType = ViewerType.JMOL.toString();
4917               }
4918               structureViewers.put(sviewid,
4919                       new StructureViewerModel(x, y, width, height, false,
4920                               false, true, structureState.getViewId(),
4921                               viewerType));
4922               // Legacy pre-2.7 conversion JAL-823 :
4923               // do not assume any view has to be linked for colour by
4924               // sequence
4925             }
4926
4927             // assemble String[] { pdb files }, String[] { id for each
4928             // file }, orig_fileloc, SequenceI[][] {{ seqs_file 1 }, {
4929             // seqs_file 2}, boolean[] {
4930             // linkAlignPanel,superposeWithAlignpanel}} from hash
4931             StructureViewerModel jmoldat = structureViewers.get(sviewid);
4932             jmoldat.setAlignWithPanel(jmoldat.isAlignWithPanel()
4933                     || structureState.isAlignwithAlignPanel());
4934
4935             /*
4936              * Default colour by linked panel to false if not specified (e.g.
4937              * for pre-2.7 projects)
4938              */
4939             boolean colourWithAlignPanel = jmoldat.isColourWithAlignPanel();
4940             colourWithAlignPanel |= structureState.isColourwithAlignPanel();
4941             jmoldat.setColourWithAlignPanel(colourWithAlignPanel);
4942
4943             /*
4944              * Default colour by viewer to true if not specified (e.g. for
4945              * pre-2.7 projects)
4946              */
4947             boolean colourByViewer = jmoldat.isColourByViewer();
4948             colourByViewer &= structureState.isColourByJmol();
4949             jmoldat.setColourByViewer(colourByViewer);
4950
4951             if (jmoldat.getStateData().length() < structureState.getValue()
4952                     /*Content()*/.length())
4953             {
4954               jmoldat.setStateData(structureState.getValue());// Content());
4955             }
4956             if (pdbid.getFile() != null)
4957             {
4958               File mapkey = new File(pdbid.getFile());
4959               StructureData seqstrmaps = jmoldat.getFileData().get(mapkey);
4960               if (seqstrmaps == null)
4961               {
4962                 jmoldat.getFileData().put(mapkey,
4963                         seqstrmaps = jmoldat.new StructureData(pdbFile,
4964                                 pdbid.getId()));
4965               }
4966               if (!seqstrmaps.getSeqList().contains(seq))
4967               {
4968                 seqstrmaps.getSeqList().add(seq);
4969                 // TODO and chains?
4970               }
4971             }
4972             else
4973             {
4974               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");
4975               Console.warn(errorMessage);
4976             }
4977           }
4978         }
4979       }
4980     }
4981     // Instantiate the associated structure views
4982     for (Entry<String, StructureViewerModel> entry : structureViewers
4983             .entrySet())
4984     {
4985       try
4986       {
4987         createOrLinkStructureViewer(entry, af, ap, jprovider);
4988       } catch (Exception e)
4989       {
4990         jalview.bin.Console.errPrintln(
4991                 "Error loading structure viewer: " + e.getMessage());
4992         // failed - try the next one
4993       }
4994     }
4995   }
4996
4997   /**
4998    * 
4999    * @param viewerData
5000    * @param af
5001    * @param ap
5002    * @param jprovider
5003    */
5004   protected void createOrLinkStructureViewer(
5005           Entry<String, StructureViewerModel> viewerData, AlignFrame af,
5006           AlignmentPanel ap, jarInputStreamProvider jprovider)
5007   {
5008     final StructureViewerModel stateData = viewerData.getValue();
5009
5010     /*
5011      * Search for any viewer windows already open from other alignment views
5012      * that exactly match the stored structure state
5013      */
5014     StructureViewerBase comp = findMatchingViewer(viewerData);
5015
5016     if (comp != null)
5017     {
5018       linkStructureViewer(ap, comp, stateData);
5019       return;
5020     }
5021
5022     String type = stateData.getType();
5023     try
5024     {
5025       ViewerType viewerType = ViewerType.valueOf(type);
5026       createStructureViewer(viewerType, viewerData, af, jprovider);
5027     } catch (IllegalArgumentException | NullPointerException e)
5028     {
5029       // TODO JAL-3619 show error dialog / offer an alternative viewer
5030       Console.error("Invalid structure viewer type: " + type);
5031     }
5032   }
5033
5034   /**
5035    * Generates a name for the entry in the project jar file to hold state
5036    * information for a structure viewer
5037    * 
5038    * @param viewId
5039    * @return
5040    */
5041   protected String getViewerJarEntryName(String viewId)
5042   {
5043     return VIEWER_PREFIX + viewId;
5044   }
5045
5046   /**
5047    * Returns any open frame that matches given structure viewer data. The match
5048    * is based on the unique viewId, or (for older project versions) the frame's
5049    * geometry.
5050    * 
5051    * @param viewerData
5052    * @return
5053    */
5054   protected StructureViewerBase findMatchingViewer(
5055           Entry<String, StructureViewerModel> viewerData)
5056   {
5057     final String sviewid = viewerData.getKey();
5058     final StructureViewerModel svattrib = viewerData.getValue();
5059     StructureViewerBase comp = null;
5060     JInternalFrame[] frames = getAllFrames();
5061     for (JInternalFrame frame : frames)
5062     {
5063       if (frame instanceof StructureViewerBase)
5064       {
5065         /*
5066          * Post jalview 2.4 schema includes structure view id
5067          */
5068         if (sviewid != null && ((StructureViewerBase) frame).getViewId()
5069                 .equals(sviewid))
5070         {
5071           comp = (StructureViewerBase) frame;
5072           break; // break added in 2.9
5073         }
5074         /*
5075          * Otherwise test for matching position and size of viewer frame
5076          */
5077         else if (frame.getX() == svattrib.getX()
5078                 && frame.getY() == svattrib.getY()
5079                 && frame.getHeight() == svattrib.getHeight()
5080                 && frame.getWidth() == svattrib.getWidth())
5081         {
5082           comp = (StructureViewerBase) frame;
5083           // no break in faint hope of an exact match on viewId
5084         }
5085       }
5086     }
5087     return comp;
5088   }
5089
5090   /**
5091    * Link an AlignmentPanel to an existing structure viewer.
5092    * 
5093    * @param ap
5094    * @param viewer
5095    * @param oldFiles
5096    * @param useinViewerSuperpos
5097    * @param usetoColourbyseq
5098    * @param viewerColouring
5099    */
5100   protected void linkStructureViewer(AlignmentPanel ap,
5101           StructureViewerBase viewer, StructureViewerModel stateData)
5102   {
5103     // NOTE: if the jalview project is part of a shared session then
5104     // view synchronization should/could be done here.
5105
5106     final boolean useinViewerSuperpos = stateData.isAlignWithPanel();
5107     final boolean usetoColourbyseq = stateData.isColourWithAlignPanel();
5108     final boolean viewerColouring = stateData.isColourByViewer();
5109     Map<File, StructureData> oldFiles = stateData.getFileData();
5110
5111     /*
5112      * Add mapping for sequences in this view to an already open viewer
5113      */
5114     final AAStructureBindingModel binding = viewer.getBinding();
5115     for (File id : oldFiles.keySet())
5116     {
5117       // add this and any other pdb files that should be present in the
5118       // viewer
5119       StructureData filedat = oldFiles.get(id);
5120       String pdbFile = filedat.getFilePath();
5121       SequenceI[] seq = filedat.getSeqList().toArray(new SequenceI[0]);
5122       binding.getSsm().setMapping(seq, null, pdbFile, DataSourceType.FILE,
5123               null);
5124       binding.addSequenceForStructFile(pdbFile, seq);
5125     }
5126     // and add the AlignmentPanel's reference to the view panel
5127     viewer.addAlignmentPanel(ap);
5128     if (useinViewerSuperpos)
5129     {
5130       viewer.useAlignmentPanelForSuperposition(ap);
5131     }
5132     else
5133     {
5134       viewer.excludeAlignmentPanelForSuperposition(ap);
5135     }
5136     if (usetoColourbyseq)
5137     {
5138       viewer.useAlignmentPanelForColourbyseq(ap, !viewerColouring);
5139     }
5140     else
5141     {
5142       viewer.excludeAlignmentPanelForColourbyseq(ap);
5143     }
5144   }
5145
5146   /**
5147    * Get all frames within the Desktop.
5148    * 
5149    * @return
5150    */
5151   protected JInternalFrame[] getAllFrames()
5152   {
5153     JInternalFrame[] frames = null;
5154     // TODO is this necessary - is it safe - risk of hanging?
5155     do
5156     {
5157       try
5158       {
5159         frames = Desktop.desktop.getAllFrames();
5160       } catch (ArrayIndexOutOfBoundsException e)
5161       {
5162         // occasional No such child exceptions are thrown here...
5163         try
5164         {
5165           Thread.sleep(10);
5166         } catch (InterruptedException f)
5167         {
5168         }
5169       }
5170     } while (frames == null);
5171     return frames;
5172   }
5173
5174   /**
5175    * Answers true if 'version' is equal to or later than 'supported', where each
5176    * is formatted as major/minor versions like "2.8.3" or "2.3.4b1" for bugfix
5177    * changes. Development and test values for 'version' are leniently treated
5178    * i.e. answer true.
5179    * 
5180    * @param supported
5181    *          - minimum version we are comparing against
5182    * @param version
5183    *          - version of data being processsed
5184    * @return true if version is equal to or later than supported
5185    */
5186   public static boolean isVersionStringLaterThan(String supported,
5187           String version)
5188   {
5189     if (supported == null || version == null
5190             || version.equalsIgnoreCase("DEVELOPMENT BUILD")
5191             || version.equalsIgnoreCase("Test")
5192             || version.equalsIgnoreCase("AUTOMATED BUILD"))
5193     {
5194       jalview.bin.Console.errPrintln("Assuming project file with "
5195               + (version == null ? "null" : version)
5196               + " is compatible with Jalview version " + supported);
5197       return true;
5198     }
5199     else
5200     {
5201       return StringUtils.compareVersions(version, supported, "b") >= 0;
5202     }
5203   }
5204
5205   Vector<JalviewStructureDisplayI> newStructureViewers = null;
5206
5207   protected void addNewStructureViewer(JalviewStructureDisplayI sview)
5208   {
5209     if (newStructureViewers != null)
5210     {
5211       sview.getBinding().setFinishedLoadingFromArchive(false);
5212       newStructureViewers.add(sview);
5213     }
5214   }
5215
5216   protected void setLoadingFinishedForNewStructureViewers()
5217   {
5218     if (newStructureViewers != null)
5219     {
5220       for (JalviewStructureDisplayI sview : newStructureViewers)
5221       {
5222         sview.getBinding().setFinishedLoadingFromArchive(true);
5223       }
5224       newStructureViewers.clear();
5225       newStructureViewers = null;
5226     }
5227   }
5228
5229   AlignFrame loadViewport(String file, List<JSeq> JSEQ,
5230           List<SequenceI> hiddenSeqs, AlignmentI al, JalviewModel jm,
5231           Viewport view, String uniqueSeqSetId, String viewId,
5232           List<JvAnnotRow> autoAlan)
5233   {
5234     AlignFrame af = null;
5235     af = new AlignFrame(al, safeInt(view.getWidth()),
5236             safeInt(view.getHeight()), uniqueSeqSetId, viewId)
5237     // {
5238     //
5239     // @Override
5240     // protected void processKeyEvent(java.awt.event.KeyEvent e) {
5241     // jalview.bin.Console.outPrintln("Jalview2XML AF " + e);
5242     // super.processKeyEvent(e);
5243     //
5244     // }
5245     //
5246     // }
5247     ;
5248
5249     af.setFileName(file, FileFormat.Jalview);
5250
5251     final AlignViewport viewport = af.getViewport();
5252     for (int i = 0; i < JSEQ.size(); i++)
5253     {
5254       int colour = safeInt(JSEQ.get(i).getColour());
5255       viewport.setSequenceColour(viewport.getAlignment().getSequenceAt(i),
5256               new Color(colour));
5257     }
5258
5259     if (al.hasSeqrep())
5260     {
5261       viewport.setColourByReferenceSeq(true);
5262       viewport.setDisplayReferenceSeq(true);
5263     }
5264
5265     viewport.setGatherViewsHere(safeBoolean(view.isGatheredViews()));
5266
5267     if (view.getSequenceSetId() != null)
5268     {
5269       AlignmentViewport av = viewportsAdded.get(uniqueSeqSetId);
5270
5271       viewport.setSequenceSetId(uniqueSeqSetId);
5272       if (av != null)
5273       {
5274         // propagate shared settings to this new view
5275         viewport.setHistoryList(av.getHistoryList());
5276         viewport.setRedoList(av.getRedoList());
5277       }
5278       else
5279       {
5280         viewportsAdded.put(uniqueSeqSetId, viewport);
5281       }
5282       // TODO: check if this method can be called repeatedly without
5283       // side-effects if alignpanel already registered.
5284       PaintRefresher.Register(af.alignPanel, uniqueSeqSetId);
5285     }
5286     // apply Hidden regions to view.
5287     if (hiddenSeqs != null)
5288     {
5289       for (int s = 0; s < JSEQ.size(); s++)
5290       {
5291         SequenceGroup hidden = new SequenceGroup();
5292         boolean isRepresentative = false;
5293         for (int r = 0; r < JSEQ.get(s).getHiddenSequences().size(); r++)
5294         {
5295           isRepresentative = true;
5296           SequenceI sequenceToHide = al
5297                   .getSequenceAt(JSEQ.get(s).getHiddenSequences().get(r));
5298           hidden.addSequence(sequenceToHide, false);
5299           // remove from hiddenSeqs list so we don't try to hide it twice
5300           hiddenSeqs.remove(sequenceToHide);
5301         }
5302         if (isRepresentative)
5303         {
5304           SequenceI representativeSequence = al.getSequenceAt(s);
5305           hidden.addSequence(representativeSequence, false);
5306           viewport.hideRepSequences(representativeSequence, hidden);
5307         }
5308       }
5309
5310       SequenceI[] hseqs = hiddenSeqs
5311               .toArray(new SequenceI[hiddenSeqs.size()]);
5312       viewport.hideSequence(hseqs);
5313
5314     }
5315     // recover view properties and display parameters
5316
5317     viewport.setShowAnnotation(safeBoolean(view.isShowAnnotation()));
5318     viewport.setAbovePIDThreshold(safeBoolean(view.isPidSelected()));
5319     final int pidThreshold = safeInt(view.getPidThreshold());
5320     viewport.setThreshold(pidThreshold);
5321
5322     viewport.setColourText(safeBoolean(view.isShowColourText()));
5323
5324     viewport.setConservationSelected(
5325             safeBoolean(view.isConservationSelected()));
5326     viewport.setIncrement(safeInt(view.getConsThreshold()));
5327     viewport.setShowJVSuffix(safeBoolean(view.isShowFullId()));
5328     viewport.setRightAlignIds(safeBoolean(view.isRightAlignIds()));
5329     viewport.setFont(
5330             new Font(view.getFontName(), safeInt(view.getFontStyle()),
5331                     safeInt(view.getFontSize())),
5332             (view.getCharWidth() != null) ? false : true);
5333     if (view.getCharWidth() != null)
5334     {
5335       viewport.setCharWidth(view.getCharWidth());
5336       viewport.setCharHeight(view.getCharHeight());
5337     }
5338     ViewStyleI vs = viewport.getViewStyle();
5339     vs.setScaleProteinAsCdna(view.isScaleProteinAsCdna());
5340     viewport.setViewStyle(vs);
5341     // TODO: allow custom charWidth/Heights to be restored by updating them
5342     // after setting font - which means set above to false
5343     viewport.setRenderGaps(safeBoolean(view.isRenderGaps()));
5344     viewport.setWrapAlignment(safeBoolean(view.isWrapAlignment()));
5345     viewport.setShowAnnotation(safeBoolean(view.isShowAnnotation()));
5346
5347     viewport.setShowBoxes(safeBoolean(view.isShowBoxes()));
5348
5349     viewport.setShowText(safeBoolean(view.isShowText()));
5350
5351     viewport.setTextColour(new Color(safeInt(view.getTextCol1())));
5352     viewport.setTextColour2(new Color(safeInt(view.getTextCol2())));
5353     viewport.setThresholdTextColour(safeInt(view.getTextColThreshold()));
5354     viewport.setShowUnconserved(view.isShowUnconserved());
5355     viewport.getRanges().setStartRes(safeInt(view.getStartRes()));
5356
5357     if (view.getViewName() != null)
5358     {
5359       viewport.setViewName(view.getViewName());
5360       af.setInitialTabVisible();
5361     }
5362     af.setBounds(safeInt(view.getXpos()), safeInt(view.getYpos()),
5363             safeInt(view.getWidth()), safeInt(view.getHeight()));
5364
5365     af.alignPanel.fontChanged(); // make sure font is updated *before* we set ID
5366                                  // width
5367     if (view.getIdWidth() == null)
5368     {
5369       if (!isVersionStringLaterThan("2.11.3", jm.getVersion()))
5370       {
5371         // Pre 2.11.3 jalview projects do not store the id width
5372         // idWidth was also calculated in a different way.
5373         viewport.setIdWidth(af.alignPanel.getLegacyIdWidth());
5374         af.alignPanel.getIdPanel().getIdCanvas().setManuallyAdjusted(true);
5375       }
5376     }
5377     else
5378     {
5379       viewport.setIdWidth(view.getIdWidth());
5380       af.alignPanel.getIdPanel().getIdCanvas()
5381               .setManuallyAdjusted(view.isIdWidthManuallyAdjusted());
5382     }
5383
5384     // startSeq set in af.alignPanel.updateLayout below
5385     af.alignPanel.updateLayout();
5386     ColourSchemeI cs = null;
5387     // apply colourschemes
5388     if (view.getBgColour() != null)
5389     {
5390       if (view.getBgColour().startsWith("ucs"))
5391       {
5392         cs = getUserColourScheme(jm, view.getBgColour());
5393       }
5394       else if (view.getBgColour().startsWith("Annotation"))
5395       {
5396         AnnotationColourScheme viewAnnColour = view.getAnnotationColours();
5397         cs = constructAnnotationColour(viewAnnColour, af, al, jm, true);
5398
5399         // annpos
5400
5401       }
5402       else
5403       {
5404         cs = ColourSchemeProperty.getColourScheme(af.getViewport(), al,
5405                 view.getBgColour());
5406       }
5407     }
5408
5409     /*
5410      * turn off 'alignment colour applies to all groups'
5411      * while restoring global colour scheme
5412      */
5413     viewport.setColourAppliesToAllGroups(false);
5414     viewport.setGlobalColourScheme(cs);
5415     viewport.getResidueShading().setThreshold(pidThreshold,
5416             view.isIgnoreGapsinConsensus());
5417     viewport.getResidueShading()
5418             .setConsensus(viewport.getSequenceConsensusHash());
5419     viewport.getResidueShading()
5420     .setSsConsensus(viewport.getSequenceSSConsensusHash());
5421     if (safeBoolean(view.isConservationSelected()) && cs != null)
5422     {
5423       viewport.getResidueShading()
5424               .setConservationInc(safeInt(view.getConsThreshold()));
5425     }
5426     af.changeColour(cs);
5427     viewport.setColourAppliesToAllGroups(true);
5428
5429     viewport.setShowSequenceFeatures(
5430             safeBoolean(view.isShowSequenceFeatures()));
5431
5432     viewport.setCentreColumnLabels(view.isCentreColumnLabels());
5433     viewport.setIgnoreGapsConsensus(view.isIgnoreGapsinConsensus(), null);
5434     viewport.setFollowHighlight(view.isFollowHighlight());
5435     viewport.followSelection = view.isFollowSelection();
5436     viewport.setShowConsensusHistogram(view.isShowConsensusHistogram());
5437     viewport.setShowSequenceLogo(view.isShowSequenceLogo());
5438     viewport.setNormaliseSequenceLogo(view.isNormaliseSequenceLogo());
5439     viewport.setShowDBRefs(safeBoolean(view.isShowDbRefTooltip()));
5440     viewport.setShowNPFeats(safeBoolean(view.isShowNPfeatureTooltip()));
5441     viewport.setShowGroupConsensus(view.isShowGroupConsensus());
5442     viewport.setShowGroupConservation(view.isShowGroupConservation());
5443     viewport.setShowComplementFeatures(view.isShowComplementFeatures());
5444     viewport.setShowComplementFeaturesOnTop(
5445             view.isShowComplementFeaturesOnTop());
5446
5447     // recover feature settings
5448     if (jm.getFeatureSettings() != null)
5449     {
5450       FeatureRendererModel fr = af.alignPanel.getSeqPanel().seqCanvas
5451               .getFeatureRenderer();
5452       FeaturesDisplayed fdi;
5453       viewport.setFeaturesDisplayed(fdi = new FeaturesDisplayed());
5454       String[] renderOrder = new String[jm.getFeatureSettings().getSetting()
5455               .size()];
5456       Map<String, FeatureColourI> featureColours = new Hashtable<>();
5457       Map<String, Float> featureOrder = new Hashtable<>();
5458
5459       for (int fs = 0; fs < jm.getFeatureSettings().getSetting()
5460               .size(); fs++)
5461       {
5462         Setting setting = jm.getFeatureSettings().getSetting().get(fs);
5463         String featureType = setting.getType();
5464
5465         /*
5466          * restore feature filters (if any)
5467          */
5468         jalview.xml.binding.jalview.FeatureMatcherSet filters = setting
5469                 .getMatcherSet();
5470         if (filters != null)
5471         {
5472           FeatureMatcherSetI filter = Jalview2XML.parseFilter(featureType,
5473                   filters);
5474           if (!filter.isEmpty())
5475           {
5476             fr.setFeatureFilter(featureType, filter);
5477           }
5478         }
5479
5480         /*
5481          * restore feature colour scheme
5482          */
5483         Color maxColour = new Color(setting.getColour());
5484         if (setting.getMincolour() != null)
5485         {
5486           /*
5487            * minColour is always set unless a simple colour
5488            * (including for colour by label though it doesn't use it)
5489            */
5490           Color minColour = new Color(setting.getMincolour().intValue());
5491           Color noValueColour = minColour;
5492           NoValueColour noColour = setting.getNoValueColour();
5493           if (noColour == NoValueColour.NONE)
5494           {
5495             noValueColour = null;
5496           }
5497           else if (noColour == NoValueColour.MAX)
5498           {
5499             noValueColour = maxColour;
5500           }
5501           float min = safeFloat(safeFloat(setting.getMin()));
5502           float max = setting.getMax() == null ? 1f
5503                   : setting.getMax().floatValue();
5504           FeatureColourI gc = new FeatureColour(maxColour, minColour,
5505                   maxColour, noValueColour, min, max);
5506           if (setting.getAttributeName().size() > 0)
5507           {
5508             gc.setAttributeName(setting.getAttributeName().toArray(
5509                     new String[setting.getAttributeName().size()]));
5510           }
5511           if (setting.getThreshold() != null)
5512           {
5513             gc.setThreshold(setting.getThreshold().floatValue());
5514             int threshstate = safeInt(setting.getThreshstate());
5515             // -1 = None, 0 = Below, 1 = Above threshold
5516             if (threshstate == 0)
5517             {
5518               gc.setBelowThreshold(true);
5519             }
5520             else if (threshstate == 1)
5521             {
5522               gc.setAboveThreshold(true);
5523             }
5524           }
5525           gc.setAutoScaled(true); // default
5526           if (setting.isAutoScale() != null)
5527           {
5528             gc.setAutoScaled(setting.isAutoScale());
5529           }
5530           if (setting.isColourByLabel() != null)
5531           {
5532             gc.setColourByLabel(setting.isColourByLabel());
5533           }
5534           // and put in the feature colour table.
5535           featureColours.put(featureType, gc);
5536         }
5537         else
5538         {
5539           featureColours.put(featureType, new FeatureColour(maxColour));
5540         }
5541         renderOrder[fs] = featureType;
5542         if (setting.getOrder() != null)
5543         {
5544           featureOrder.put(featureType, setting.getOrder().floatValue());
5545         }
5546         else
5547         {
5548           featureOrder.put(featureType, Float.valueOf(
5549                   fs / jm.getFeatureSettings().getSetting().size()));
5550         }
5551         if (safeBoolean(setting.isDisplay()))
5552         {
5553           fdi.setVisible(featureType);
5554         }
5555       }
5556       Map<String, Boolean> fgtable = new Hashtable<>();
5557       for (int gs = 0; gs < jm.getFeatureSettings().getGroup().size(); gs++)
5558       {
5559         Group grp = jm.getFeatureSettings().getGroup().get(gs);
5560         fgtable.put(grp.getName(), Boolean.valueOf(grp.isDisplay()));
5561       }
5562       // FeatureRendererSettings frs = new FeatureRendererSettings(renderOrder,
5563       // fgtable, featureColours, jms.getFeatureSettings().hasTransparency() ?
5564       // jms.getFeatureSettings().getTransparency() : 0.0, featureOrder);
5565       FeatureRendererSettings frs = new FeatureRendererSettings(renderOrder,
5566               fgtable, featureColours, 1.0f, featureOrder);
5567       fr.transferSettings(frs);
5568     }
5569
5570     if (view.getHiddenColumns().size() > 0)
5571     {
5572       for (int c = 0; c < view.getHiddenColumns().size(); c++)
5573       {
5574         final HiddenColumns hc = view.getHiddenColumns().get(c);
5575         viewport.hideColumns(safeInt(hc.getStart()),
5576                 safeInt(hc.getEnd()) /* +1 */);
5577       }
5578     }
5579     if (view.getCalcIdParam() != null)
5580     {
5581       for (CalcIdParam calcIdParam : view.getCalcIdParam())
5582       {
5583         if (calcIdParam != null)
5584         {
5585           if (recoverCalcIdParam(calcIdParam, viewport))
5586           {
5587           }
5588           else
5589           {
5590             Console.warn("Couldn't recover parameters for "
5591                     + calcIdParam.getCalcId());
5592           }
5593         }
5594       }
5595     }
5596     af.setMenusFromViewport(viewport);
5597     af.setTitle(view.getTitle());
5598     // TODO: we don't need to do this if the viewport is aready visible.
5599     /*
5600      * Add the AlignFrame to the desktop (it may be 'gathered' later), unless it
5601      * has a 'cdna/protein complement' view, in which case save it in order to
5602      * populate a SplitFrame once all views have been read in.
5603      */
5604     String complementaryViewId = view.getComplementId();
5605     if (complementaryViewId == null)
5606     {
5607       Desktop.addInternalFrame(af, view.getTitle(),
5608               safeInt(view.getWidth()), safeInt(view.getHeight()));
5609       // recompute any autoannotation
5610       af.alignPanel.updateAnnotation(false, true);
5611       reorderAutoannotation(af, al, autoAlan);
5612       af.alignPanel.alignmentChanged();
5613     }
5614     else
5615     {
5616       splitFrameCandidates.put(view, af);
5617     }
5618
5619     return af;
5620   }
5621
5622   /**
5623    * Reads saved data to restore Colour by Annotation settings
5624    * 
5625    * @param viewAnnColour
5626    * @param af
5627    * @param al
5628    * @param model
5629    * @param checkGroupAnnColour
5630    * @return
5631    */
5632   private ColourSchemeI constructAnnotationColour(
5633           AnnotationColourScheme viewAnnColour, AlignFrame af,
5634           AlignmentI al, JalviewModel model, boolean checkGroupAnnColour)
5635   {
5636     boolean propagateAnnColour = false;
5637     AlignmentI annAlignment = af != null ? af.getViewport().getAlignment()
5638             : al;
5639     if (checkGroupAnnColour && al.getGroups() != null
5640             && al.getGroups().size() > 0)
5641     {
5642       // pre 2.8.1 behaviour
5643       // check to see if we should transfer annotation colours
5644       propagateAnnColour = true;
5645       for (SequenceGroup sg : al.getGroups())
5646       {
5647         if (sg.getColourScheme() instanceof AnnotationColourGradient)
5648         {
5649           propagateAnnColour = false;
5650         }
5651       }
5652     }
5653
5654     /*
5655      * 2.10.2- : saved annotationId is AlignmentAnnotation.annotationId
5656      */
5657     String annotationId = viewAnnColour.getAnnotation();
5658     AlignmentAnnotation matchedAnnotation = annotationIds.get(annotationId);
5659
5660     /*
5661      * pre 2.10.2: saved annotationId is AlignmentAnnotation.label
5662      */
5663     if (matchedAnnotation == null
5664             && annAlignment.getAlignmentAnnotation() != null)
5665     {
5666       for (int i = 0; i < annAlignment.getAlignmentAnnotation().length; i++)
5667       {
5668         if (annotationId
5669                 .equals(annAlignment.getAlignmentAnnotation()[i].label))
5670         {
5671           matchedAnnotation = annAlignment.getAlignmentAnnotation()[i];
5672           break;
5673         }
5674       }
5675     }
5676     if (matchedAnnotation == null)
5677     {
5678       jalview.bin.Console
5679               .errPrintln("Failed to match annotation colour scheme for "
5680                       + annotationId);
5681       return null;
5682     }
5683     // belt-and-braces create a threshold line if the
5684     // colourscheme needs one but the matchedAnnotation doesn't have one
5685     if (safeInt(viewAnnColour.getAboveThreshold()) != 0
5686             && matchedAnnotation.getThreshold() == null)
5687     {
5688       matchedAnnotation.setThreshold(
5689               new GraphLine(safeFloat(viewAnnColour.getThreshold()),
5690                       "Threshold", Color.black));
5691     }
5692
5693     AnnotationColourGradient cs = null;
5694     if (viewAnnColour.getColourScheme().equals("None"))
5695     {
5696       cs = new AnnotationColourGradient(matchedAnnotation,
5697               new Color(safeInt(viewAnnColour.getMinColour())),
5698               new Color(safeInt(viewAnnColour.getMaxColour())),
5699               safeInt(viewAnnColour.getAboveThreshold()));
5700     }
5701     else if (viewAnnColour.getColourScheme().startsWith("ucs"))
5702     {
5703       cs = new AnnotationColourGradient(matchedAnnotation,
5704               getUserColourScheme(model, viewAnnColour.getColourScheme()),
5705               safeInt(viewAnnColour.getAboveThreshold()));
5706     }
5707     else
5708     {
5709       cs = new AnnotationColourGradient(matchedAnnotation,
5710               ColourSchemeProperty.getColourScheme(af.getViewport(), al,
5711                       viewAnnColour.getColourScheme()),
5712               safeInt(viewAnnColour.getAboveThreshold()));
5713     }
5714
5715     boolean perSequenceOnly = safeBoolean(viewAnnColour.isPerSequence());
5716     boolean useOriginalColours = safeBoolean(
5717             viewAnnColour.isPredefinedColours());
5718     cs.setSeqAssociated(perSequenceOnly);
5719     cs.setPredefinedColours(useOriginalColours);
5720
5721     if (propagateAnnColour && al.getGroups() != null)
5722     {
5723       // Also use these settings for all the groups
5724       for (int g = 0; g < al.getGroups().size(); g++)
5725       {
5726         SequenceGroup sg = al.getGroups().get(g);
5727         if (sg.getGroupColourScheme() == null)
5728         {
5729           continue;
5730         }
5731
5732         AnnotationColourGradient groupScheme = new AnnotationColourGradient(
5733                 matchedAnnotation, sg.getColourScheme(),
5734                 safeInt(viewAnnColour.getAboveThreshold()));
5735         sg.setColourScheme(groupScheme);
5736         groupScheme.setSeqAssociated(perSequenceOnly);
5737         groupScheme.setPredefinedColours(useOriginalColours);
5738       }
5739     }
5740     return cs;
5741   }
5742
5743   private void reorderAutoannotation(AlignFrame af, AlignmentI al,
5744           List<JvAnnotRow> autoAlan)
5745   {
5746     // copy over visualization settings for autocalculated annotation in the
5747     // view
5748     if (al.getAlignmentAnnotation() != null)
5749     {
5750       /**
5751        * Kludge for magic autoannotation names (see JAL-811)
5752        */
5753       String[] magicNames = new String[] { "Consensus", "Quality",
5754           "Conservation" };
5755       JvAnnotRow nullAnnot = new JvAnnotRow(-1, null);
5756       Hashtable<String, JvAnnotRow> visan = new Hashtable<>();
5757       for (String nm : magicNames)
5758       {
5759         visan.put(nm, nullAnnot);
5760       }
5761       for (JvAnnotRow auan : autoAlan)
5762       {
5763         visan.put(auan.template.label
5764                 + (auan.template.getCalcId() == null ? ""
5765                         : "\t" + auan.template.getCalcId()),
5766                 auan);
5767       }
5768       int hSize = al.getAlignmentAnnotation().length;
5769       List<JvAnnotRow> reorder = new ArrayList<>();
5770       // work through any autoCalculated annotation already on the view
5771       // removing it if it should be placed in a different location on the
5772       // annotation panel.
5773       List<String> remains = new ArrayList<>(visan.keySet());
5774       for (int h = 0; h < hSize; h++)
5775       {
5776         jalview.datamodel.AlignmentAnnotation jalan = al
5777                 .getAlignmentAnnotation()[h];
5778         if (jalan.autoCalculated)
5779         {
5780           String k;
5781           JvAnnotRow valan = visan.get(k = jalan.label);
5782           if (jalan.getCalcId() != null)
5783           {
5784             valan = visan.get(k = jalan.label + "\t" + jalan.getCalcId());
5785           }
5786
5787           if (valan != null)
5788           {
5789             // delete the auto calculated row from the alignment
5790             al.deleteAnnotation(jalan, false);
5791             remains.remove(k);
5792             hSize--;
5793             h--;
5794             if (valan != nullAnnot)
5795             {
5796               if (jalan != valan.template)
5797               {
5798                 // newly created autoannotation row instance
5799                 // so keep a reference to the visible annotation row
5800                 // and copy over all relevant attributes
5801                 if (valan.template.graphHeight >= 0)
5802
5803                 {
5804                   jalan.graphHeight = valan.template.graphHeight;
5805                 }
5806                 jalan.visible = valan.template.visible;
5807               }
5808               reorder.add(new JvAnnotRow(valan.order, jalan));
5809             }
5810           }
5811         }
5812       }
5813       // Add any (possibly stale) autocalculated rows that were not appended to
5814       // the view during construction
5815       for (String other : remains)
5816       {
5817         JvAnnotRow othera = visan.get(other);
5818         if (othera != nullAnnot && othera.template.getCalcId() != null
5819                 && othera.template.getCalcId().length() > 0)
5820         {
5821           reorder.add(othera);
5822         }
5823       }
5824       // now put the automatic annotation in its correct place
5825       int s = 0, srt[] = new int[reorder.size()];
5826       JvAnnotRow[] rws = new JvAnnotRow[reorder.size()];
5827       for (JvAnnotRow jvar : reorder)
5828       {
5829         rws[s] = jvar;
5830         srt[s++] = jvar.order;
5831       }
5832       reorder.clear();
5833       jalview.util.QuickSort.sort(srt, rws);
5834       // and re-insert the annotation at its correct position
5835       for (JvAnnotRow jvar : rws)
5836       {
5837         al.addAnnotation(jvar.template, jvar.order);
5838       }
5839       af.alignPanel.adjustAnnotationHeight();
5840     }
5841   }
5842
5843   Hashtable skipList = null;
5844
5845   /**
5846    * TODO remove this method
5847    * 
5848    * @param view
5849    * @return AlignFrame bound to sequenceSetId from view, if one exists. private
5850    *         AlignFrame getSkippedFrame(Viewport view) { if (skipList==null) {
5851    *         throw new Error("Implementation Error. No skipList defined for this
5852    *         Jalview2XML instance."); } return (AlignFrame)
5853    *         skipList.get(view.getSequenceSetId()); }
5854    */
5855
5856   /**
5857    * Check if the Jalview view contained in object should be skipped or not.
5858    * 
5859    * @param object
5860    * @return true if view's sequenceSetId is a key in skipList
5861    */
5862   private boolean skipViewport(JalviewModel object)
5863   {
5864     if (skipList == null)
5865     {
5866       return false;
5867     }
5868     String id = object.getViewport().get(0).getSequenceSetId();
5869     if (skipList.containsKey(id))
5870     {
5871       Console.debug("Skipping seuqence set id " + id);
5872       return true;
5873     }
5874     return false;
5875   }
5876
5877   public void addToSkipList(AlignFrame af)
5878   {
5879     if (skipList == null)
5880     {
5881       skipList = new Hashtable();
5882     }
5883     skipList.put(af.getViewport().getSequenceSetId(), af);
5884   }
5885
5886   public void clearSkipList()
5887   {
5888     if (skipList != null)
5889     {
5890       skipList.clear();
5891       skipList = null;
5892     }
5893   }
5894
5895   private void recoverDatasetFor(SequenceSet vamsasSet, AlignmentI al,
5896           boolean ignoreUnrefed, String uniqueSeqSetId)
5897   {
5898     jalview.datamodel.AlignmentI ds = getDatasetFor(
5899             vamsasSet.getDatasetId());
5900     AlignmentI xtant_ds = ds;
5901     if (xtant_ds == null)
5902     {
5903       // good chance we are about to create a new dataset, but check if we've
5904       // seen some of the dataset sequence IDs before.
5905       // TODO: skip this check if we are working with project generated by
5906       // version 2.11 or later
5907       xtant_ds = checkIfHasDataset(vamsasSet.getSequence());
5908       if (xtant_ds != null)
5909       {
5910         ds = xtant_ds;
5911         addDatasetRef(vamsasSet.getDatasetId(), ds);
5912       }
5913     }
5914     Vector<SequenceI> dseqs = null;
5915     if (!ignoreUnrefed)
5916     {
5917       // recovering an alignment View
5918       AlignmentI seqSetDS = getDatasetFor(UNIQSEQSETID + uniqueSeqSetId);
5919       if (seqSetDS != null)
5920       {
5921         if (ds != null && ds != seqSetDS)
5922         {
5923           Console.warn(
5924                   "JAL-3171 regression: Overwriting a dataset reference for an alignment"
5925                           + " - CDS/Protein crossreference data may be lost");
5926           if (xtant_ds != null)
5927           {
5928             // This can only happen if the unique sequence set ID was bound to a
5929             // dataset that did not contain any of the sequences in the view
5930             // currently being restored.
5931             Console.warn(
5932                     "JAL-3171 SERIOUS!  TOTAL CONFUSION - please consider contacting the Jalview Development team so they can investigate why your project caused this message to be displayed.");
5933           }
5934         }
5935         ds = seqSetDS;
5936         addDatasetRef(vamsasSet.getDatasetId(), ds);
5937       }
5938     }
5939     if (ds == null)
5940     {
5941       // try even harder to restore dataset
5942       AlignmentI xtantDS = checkIfHasDataset(vamsasSet.getSequence());
5943       // create a list of new dataset sequences
5944       dseqs = new Vector<>();
5945     }
5946     for (int i = 0, iSize = vamsasSet.getSequence().size(); i < iSize; i++)
5947     {
5948       Sequence vamsasSeq = vamsasSet.getSequence().get(i);
5949       ensureJalviewDatasetSequence(vamsasSeq, ds, dseqs, ignoreUnrefed, i);
5950     }
5951     // create a new dataset
5952     if (ds == null)
5953     {
5954       SequenceI[] dsseqs = new SequenceI[dseqs.size()];
5955       dseqs.copyInto(dsseqs);
5956       ds = new jalview.datamodel.Alignment(dsseqs);
5957       Console.debug("Created new dataset " + vamsasSet.getDatasetId()
5958               + " for alignment " + System.identityHashCode(al));
5959       addDatasetRef(vamsasSet.getDatasetId(), ds);
5960     }
5961     // set the dataset for the newly imported alignment.
5962     if (al.getDataset() == null && !ignoreUnrefed)
5963     {
5964       al.setDataset(ds);
5965       // register dataset for the alignment's uniqueSeqSetId for legacy projects
5966       addDatasetRef(UNIQSEQSETID + uniqueSeqSetId, ds);
5967     }
5968     updateSeqDatasetBinding(vamsasSet.getSequence(), ds);
5969   }
5970
5971   /**
5972    * XML dataset sequence ID to materialised dataset reference
5973    */
5974   HashMap<String, AlignmentI> seqToDataset = new HashMap<>();
5975
5976   /**
5977    * @return the first materialised dataset reference containing a dataset
5978    *         sequence referenced in the given view
5979    * @param list
5980    *          - sequences from the view
5981    */
5982   AlignmentI checkIfHasDataset(List<Sequence> list)
5983   {
5984     for (Sequence restoredSeq : list)
5985     {
5986       AlignmentI datasetFor = seqToDataset.get(restoredSeq.getDsseqid());
5987       if (datasetFor != null)
5988       {
5989         return datasetFor;
5990       }
5991     }
5992     return null;
5993   }
5994
5995   /**
5996    * Register ds as the containing dataset for the dataset sequences referenced
5997    * by sequences in list
5998    * 
5999    * @param list
6000    *          - sequences in a view
6001    * @param ds
6002    */
6003   void updateSeqDatasetBinding(List<Sequence> list, AlignmentI ds)
6004   {
6005     for (Sequence restoredSeq : list)
6006     {
6007       AlignmentI prevDS = seqToDataset.put(restoredSeq.getDsseqid(), ds);
6008       if (prevDS != null && prevDS != ds)
6009       {
6010         Console.warn("Dataset sequence appears in many datasets: "
6011                 + restoredSeq.getDsseqid());
6012         // TODO: try to merge!
6013       }
6014     }
6015   }
6016
6017   /**
6018    * 
6019    * @param vamsasSeq
6020    *          sequence definition to create/merge dataset sequence for
6021    * @param ds
6022    *          dataset alignment
6023    * @param dseqs
6024    *          vector to add new dataset sequence to
6025    * @param ignoreUnrefed
6026    *          - when true, don't create new sequences from vamsasSeq if it's id
6027    *          doesn't already have an asssociated Jalview sequence.
6028    * @param vseqpos
6029    *          - used to reorder the sequence in the alignment according to the
6030    *          vamsasSeq array ordering, to preserve ordering of dataset
6031    */
6032   private void ensureJalviewDatasetSequence(Sequence vamsasSeq,
6033           AlignmentI ds, Vector<SequenceI> dseqs, boolean ignoreUnrefed,
6034           int vseqpos)
6035   {
6036     // JBP TODO: Check this is called for AlCodonFrames to support recovery of
6037     // xRef Codon Maps
6038     SequenceI sq = seqRefIds.get(vamsasSeq.getId());
6039     boolean reorder = false;
6040     SequenceI dsq = null;
6041     if (sq != null && sq.getDatasetSequence() != null)
6042     {
6043       dsq = sq.getDatasetSequence();
6044     }
6045     else
6046     {
6047       reorder = true;
6048     }
6049     if (sq == null && ignoreUnrefed)
6050     {
6051       return;
6052     }
6053     String sqid = vamsasSeq.getDsseqid();
6054     if (dsq == null)
6055     {
6056       // need to create or add a new dataset sequence reference to this sequence
6057       if (sqid != null)
6058       {
6059         dsq = seqRefIds.get(sqid);
6060       }
6061       // check again
6062       if (dsq == null)
6063       {
6064         // make a new dataset sequence
6065         dsq = sq.createDatasetSequence();
6066         if (sqid == null)
6067         {
6068           // make up a new dataset reference for this sequence
6069           sqid = seqHash(dsq);
6070         }
6071         dsq.setVamsasId(uniqueSetSuffix + sqid);
6072         seqRefIds.put(sqid, dsq);
6073         if (ds == null)
6074         {
6075           if (dseqs != null)
6076           {
6077             dseqs.addElement(dsq);
6078           }
6079         }
6080         else
6081         {
6082           ds.addSequence(dsq);
6083         }
6084       }
6085       else
6086       {
6087         if (sq != dsq)
6088         { // make this dataset sequence sq's dataset sequence
6089           sq.setDatasetSequence(dsq);
6090           // and update the current dataset alignment
6091           if (ds == null)
6092           {
6093             if (dseqs != null)
6094             {
6095               if (!dseqs.contains(dsq))
6096               {
6097                 dseqs.add(dsq);
6098               }
6099             }
6100             else
6101             {
6102               if (ds.findIndex(dsq) < 0)
6103               {
6104                 ds.addSequence(dsq);
6105               }
6106             }
6107           }
6108         }
6109       }
6110     }
6111     // TODO: refactor this as a merge dataset sequence function
6112     // now check that sq (the dataset sequence) sequence really is the union of
6113     // all references to it
6114     // boolean pre = sq.getStart() < dsq.getStart();
6115     // boolean post = sq.getEnd() > dsq.getEnd();
6116     // if (pre || post)
6117     if (sq != dsq)
6118     {
6119       // StringBuffer sb = new StringBuffer();
6120       String newres = jalview.analysis.AlignSeq.extractGaps(
6121               jalview.util.Comparison.GapChars, sq.getSequenceAsString());
6122       if (!newres.equalsIgnoreCase(dsq.getSequenceAsString())
6123               && newres.length() > dsq.getLength())
6124       {
6125         // Update with the longer sequence.
6126         synchronized (dsq)
6127         {
6128           /*
6129            * if (pre) { sb.insert(0, newres .substring(0, dsq.getStart() -
6130            * sq.getStart())); dsq.setStart(sq.getStart()); } if (post) {
6131            * sb.append(newres.substring(newres.length() - sq.getEnd() -
6132            * dsq.getEnd())); dsq.setEnd(sq.getEnd()); }
6133            */
6134           dsq.setSequence(newres);
6135         }
6136         // TODO: merges will never happen if we 'know' we have the real dataset
6137         // sequence - this should be detected when id==dssid
6138         jalview.bin.Console.errPrintln(
6139                 "DEBUG Notice:  Merged dataset sequence (if you see this often, post at http://issues.jalview.org/browse/JAL-1474)"); // ("
6140         // + (pre ? "prepended" : "") + " "
6141         // + (post ? "appended" : ""));
6142       }
6143     }
6144     else
6145     {
6146       // sequence refs are identical. We may need to update the existing dataset
6147       // alignment with this one, though.
6148       if (ds != null && dseqs == null)
6149       {
6150         int opos = ds.findIndex(dsq);
6151         SequenceI tseq = null;
6152         if (opos != -1 && vseqpos != opos)
6153         {
6154           // remove from old position
6155           ds.deleteSequence(dsq);
6156         }
6157         if (vseqpos < ds.getHeight())
6158         {
6159           if (vseqpos != opos)
6160           {
6161             // save sequence at destination position
6162             tseq = ds.getSequenceAt(vseqpos);
6163             ds.replaceSequenceAt(vseqpos, dsq);
6164             ds.addSequence(tseq);
6165           }
6166         }
6167         else
6168         {
6169           ds.addSequence(dsq);
6170         }
6171       }
6172     }
6173   }
6174
6175   /*
6176    * TODO use AlignmentI here and in related methods - needs
6177    * AlignmentI.getDataset() changed to return AlignmentI instead of Alignment
6178    */
6179   Hashtable<String, AlignmentI> datasetIds = null;
6180
6181   IdentityHashMap<AlignmentI, String> dataset2Ids = null;
6182
6183   private AlignmentI getDatasetFor(String datasetId)
6184   {
6185     if (datasetIds == null)
6186     {
6187       datasetIds = new Hashtable<>();
6188       return null;
6189     }
6190     if (datasetIds.containsKey(datasetId))
6191     {
6192       return datasetIds.get(datasetId);
6193     }
6194     return null;
6195   }
6196
6197   private void addDatasetRef(String datasetId, AlignmentI dataset)
6198   {
6199     if (datasetIds == null)
6200     {
6201       datasetIds = new Hashtable<>();
6202     }
6203     datasetIds.put(datasetId, dataset);
6204   }
6205
6206   /**
6207    * make a new dataset ID for this jalview dataset alignment
6208    * 
6209    * @param dataset
6210    * @return
6211    */
6212   private String getDatasetIdRef(AlignmentI dataset)
6213   {
6214     if (dataset.getDataset() != null)
6215     {
6216       Console.warn(
6217               "Serious issue!  Dataset Object passed to getDatasetIdRef is not a Jalview DATASET alignment...");
6218     }
6219     String datasetId = makeHashCode(dataset, null);
6220     if (datasetId == null)
6221     {
6222       // make a new datasetId and record it
6223       if (dataset2Ids == null)
6224       {
6225         dataset2Ids = new IdentityHashMap<>();
6226       }
6227       else
6228       {
6229         datasetId = dataset2Ids.get(dataset);
6230       }
6231       if (datasetId == null)
6232       {
6233         datasetId = "ds" + dataset2Ids.size() + 1;
6234         dataset2Ids.put(dataset, datasetId);
6235       }
6236     }
6237     return datasetId;
6238   }
6239
6240   /**
6241    * Add any saved DBRefEntry's to the sequence. An entry flagged as 'locus' is
6242    * constructed as a special subclass GeneLocus.
6243    * 
6244    * @param datasetSequence
6245    * @param sequence
6246    */
6247   private void addDBRefs(SequenceI datasetSequence, Sequence sequence)
6248   {
6249     for (int d = 0; d < sequence.getDBRef().size(); d++)
6250     {
6251       DBRef dr = sequence.getDBRef().get(d);
6252       DBRefEntry entry;
6253       if (dr.isLocus())
6254       {
6255         entry = new GeneLocus(dr.getSource(), dr.getVersion(),
6256                 dr.getAccessionId());
6257       }
6258       else
6259       {
6260         entry = new DBRefEntry(dr.getSource(), dr.getVersion(),
6261                 dr.getAccessionId());
6262       }
6263       if (dr.getMapping() != null)
6264       {
6265         entry.setMap(addMapping(dr.getMapping()));
6266       }
6267       entry.setCanonical(dr.isCanonical());
6268       datasetSequence.addDBRef(entry);
6269     }
6270   }
6271
6272   private jalview.datamodel.Mapping addMapping(Mapping m)
6273   {
6274     SequenceI dsto = null;
6275     // Mapping m = dr.getMapping();
6276     int fr[] = new int[m.getMapListFrom().size() * 2];
6277     Iterator<MapListFrom> from = m.getMapListFrom().iterator();// enumerateMapListFrom();
6278     for (int _i = 0; from.hasNext(); _i += 2)
6279     {
6280       MapListFrom mf = from.next();
6281       fr[_i] = mf.getStart();
6282       fr[_i + 1] = mf.getEnd();
6283     }
6284     int fto[] = new int[m.getMapListTo().size() * 2];
6285     Iterator<MapListTo> to = m.getMapListTo().iterator();// enumerateMapListTo();
6286     for (int _i = 0; to.hasNext(); _i += 2)
6287     {
6288       MapListTo mf = to.next();
6289       fto[_i] = mf.getStart();
6290       fto[_i + 1] = mf.getEnd();
6291     }
6292     jalview.datamodel.Mapping jmap = new jalview.datamodel.Mapping(dsto, fr,
6293             fto, m.getMapFromUnit().intValue(),
6294             m.getMapToUnit().intValue());
6295
6296     /*
6297      * (optional) choice of dseqFor or Sequence
6298      */
6299     if (m.getDseqFor() != null)
6300     {
6301       String dsfor = m.getDseqFor();
6302       if (seqRefIds.containsKey(dsfor))
6303       {
6304         /*
6305          * recover from hash
6306          */
6307         jmap.setTo(seqRefIds.get(dsfor));
6308       }
6309       else
6310       {
6311         frefedSequence.add(newMappingRef(dsfor, jmap));
6312       }
6313     }
6314     else if (m.getSequence() != null)
6315     {
6316       /*
6317        * local sequence definition
6318        */
6319       Sequence ms = m.getSequence();
6320       SequenceI djs = null;
6321       String sqid = ms.getDsseqid();
6322       if (sqid != null && sqid.length() > 0)
6323       {
6324         /*
6325          * recover dataset sequence
6326          */
6327         djs = seqRefIds.get(sqid);
6328       }
6329       else
6330       {
6331         jalview.bin.Console.errPrintln(
6332                 "Warning - making up dataset sequence id for DbRef sequence map reference");
6333         sqid = ((Object) ms).toString(); // make up a new hascode for
6334         // undefined dataset sequence hash
6335         // (unlikely to happen)
6336       }
6337
6338       if (djs == null)
6339       {
6340         /**
6341          * make a new dataset sequence and add it to refIds hash
6342          */
6343         djs = new jalview.datamodel.Sequence(ms.getName(),
6344                 ms.getSequence());
6345         djs.setStart(jmap.getMap().getToLowest());
6346         djs.setEnd(jmap.getMap().getToHighest());
6347         djs.setVamsasId(uniqueSetSuffix + sqid);
6348         jmap.setTo(djs);
6349         incompleteSeqs.put(sqid, djs);
6350         seqRefIds.put(sqid, djs);
6351
6352       }
6353       Console.debug("about to recurse on addDBRefs.");
6354       addDBRefs(djs, ms);
6355
6356     }
6357
6358     return jmap;
6359   }
6360
6361   /**
6362    * Provides a 'copy' of an alignment view (on action New View) by 'saving' the
6363    * view as XML (but not to file), and then reloading it
6364    * 
6365    * @param ap
6366    * @return
6367    */
6368   public AlignmentPanel copyAlignPanel(AlignmentPanel ap)
6369   {
6370     initSeqRefs();
6371     JalviewModel jm = saveState(ap, null, null, null);
6372
6373     addDatasetRef(
6374             jm.getVamsasModel().getSequenceSet().get(0).getDatasetId(),
6375             ap.getAlignment().getDataset());
6376
6377     uniqueSetSuffix = "";
6378     // jm.getJalviewModelSequence().getViewport(0).setId(null);
6379     jm.getViewport().get(0).setId(null);
6380     // we don't overwrite the view we just copied
6381
6382     if (this.frefedSequence == null)
6383     {
6384       frefedSequence = new Vector<>();
6385     }
6386
6387     viewportsAdded.clear();
6388
6389     AlignFrame af = loadFromObject(jm, null, false, null);
6390     af.getAlignPanels().clear();
6391     af.closeMenuItem_actionPerformed(true);
6392
6393     /*
6394      * if(ap.av.getAlignment().getAlignmentAnnotation()!=null) { for(int i=0;
6395      * i<ap.av.getAlignment().getAlignmentAnnotation().length; i++) {
6396      * if(!ap.av.getAlignment().getAlignmentAnnotation()[i].autoCalculated) {
6397      * af.alignPanel.av.getAlignment().getAlignmentAnnotation()[i] =
6398      * ap.av.getAlignment().getAlignmentAnnotation()[i]; } } }
6399      */
6400
6401     return af.alignPanel;
6402   }
6403
6404   private Hashtable jvids2vobj;
6405
6406   /**
6407    * set the object to ID mapping tables used to write/recover objects and XML
6408    * ID strings for the jalview project. If external tables are provided then
6409    * finalize and clearSeqRefs will not clear the tables when the Jalview2XML
6410    * object goes out of scope. - also populates the datasetIds hashtable with
6411    * alignment objects containing dataset sequences
6412    * 
6413    * @param vobj2jv
6414    *          Map from ID strings to jalview datamodel
6415    * @param jv2vobj
6416    *          Map from jalview datamodel to ID strings
6417    * 
6418    * 
6419    */
6420   public void setObjectMappingTables(Hashtable vobj2jv,
6421           IdentityHashMap jv2vobj)
6422   {
6423     this.jv2vobj = jv2vobj;
6424     this.vobj2jv = vobj2jv;
6425     Iterator ds = jv2vobj.keySet().iterator();
6426     String id;
6427     while (ds.hasNext())
6428     {
6429       Object jvobj = ds.next();
6430       id = jv2vobj.get(jvobj).toString();
6431       if (jvobj instanceof jalview.datamodel.Alignment)
6432       {
6433         if (((jalview.datamodel.Alignment) jvobj).getDataset() == null)
6434         {
6435           addDatasetRef(id, (jalview.datamodel.Alignment) jvobj);
6436         }
6437       }
6438       else if (jvobj instanceof jalview.datamodel.Sequence)
6439       {
6440         // register sequence object so the XML parser can recover it.
6441         if (seqRefIds == null)
6442         {
6443           seqRefIds = new HashMap<>();
6444         }
6445         if (seqsToIds == null)
6446         {
6447           seqsToIds = new IdentityHashMap<>();
6448         }
6449         seqRefIds.put(jv2vobj.get(jvobj).toString(), (SequenceI) jvobj);
6450         seqsToIds.put((SequenceI) jvobj, id);
6451       }
6452       else if (jvobj instanceof jalview.datamodel.AlignmentAnnotation)
6453       {
6454         String anid;
6455         AlignmentAnnotation jvann = (AlignmentAnnotation) jvobj;
6456         annotationIds.put(anid = jv2vobj.get(jvobj).toString(), jvann);
6457         if (jvann.annotationId == null)
6458         {
6459           jvann.annotationId = anid;
6460         }
6461         if (!jvann.annotationId.equals(anid))
6462         {
6463           // TODO verify that this is the correct behaviour
6464           Console.warn("Overriding Annotation ID for " + anid
6465                   + " from different id : " + jvann.annotationId);
6466           jvann.annotationId = anid;
6467         }
6468       }
6469       else if (jvobj instanceof String)
6470       {
6471         if (jvids2vobj == null)
6472         {
6473           jvids2vobj = new Hashtable();
6474           jvids2vobj.put(jvobj, jv2vobj.get(jvobj).toString());
6475         }
6476       }
6477       else
6478       {
6479         Console.debug("Ignoring " + jvobj.getClass() + " (ID = " + id);
6480       }
6481     }
6482   }
6483
6484   /**
6485    * set the uniqueSetSuffix used to prefix/suffix object IDs for jalview
6486    * objects created from the project archive. If string is null (default for
6487    * construction) then suffix will be set automatically.
6488    * 
6489    * @param string
6490    */
6491   public void setUniqueSetSuffix(String string)
6492   {
6493     uniqueSetSuffix = string;
6494
6495   }
6496
6497   /**
6498    * uses skipList2 as the skipList for skipping views on sequence sets
6499    * associated with keys in the skipList
6500    * 
6501    * @param skipList2
6502    */
6503   public void setSkipList(Hashtable skipList2)
6504   {
6505     skipList = skipList2;
6506   }
6507
6508   /**
6509    * Reads the jar entry of given name and returns its contents, or null if the
6510    * entry is not found.
6511    * 
6512    * @param jprovider
6513    * @param jarEntryName
6514    * @return
6515    */
6516   protected String readJarEntry(jarInputStreamProvider jprovider,
6517           String jarEntryName)
6518   {
6519     String result = null;
6520     BufferedReader in = null;
6521
6522     try
6523     {
6524       /*
6525        * Reopen the jar input stream and traverse its entries to find a matching
6526        * name
6527        */
6528       JarInputStream jin = jprovider.getJarInputStream();
6529       JarEntry entry = null;
6530       do
6531       {
6532         entry = jin.getNextJarEntry();
6533       } while (entry != null && !entry.getName().equals(jarEntryName));
6534
6535       if (entry != null)
6536       {
6537         StringBuilder out = new StringBuilder(256);
6538         in = new BufferedReader(new InputStreamReader(jin, UTF_8));
6539         String data;
6540
6541         while ((data = in.readLine()) != null)
6542         {
6543           out.append(data);
6544         }
6545         result = out.toString();
6546       }
6547       else
6548       {
6549         Console.warn(
6550                 "Couldn't find entry in Jalview Jar for " + jarEntryName);
6551       }
6552     } catch (Exception ex)
6553     {
6554       ex.printStackTrace();
6555     } finally
6556     {
6557       if (in != null)
6558       {
6559         try
6560         {
6561           in.close();
6562         } catch (IOException e)
6563         {
6564           // ignore
6565         }
6566       }
6567     }
6568
6569     return result;
6570   }
6571
6572   /**
6573    * Returns an incrementing counter (0, 1, 2...)
6574    * 
6575    * @return
6576    */
6577   private synchronized int nextCounter()
6578   {
6579     return counter++;
6580   }
6581
6582   /**
6583    * Loads any saved PCA viewers
6584    * 
6585    * @param jms
6586    * @param ap
6587    */
6588   protected void loadPCAViewers(JalviewModel model, AlignmentPanel ap)
6589   {
6590     try
6591     {
6592       List<PcaViewer> pcaviewers = model.getPcaViewer();
6593       for (PcaViewer viewer : pcaviewers)
6594       {
6595         if (isPasimap(viewer))
6596         {
6597           continue;
6598         }
6599         String modelName = viewer.getScoreModelName();
6600         SimilarityParamsI params = new SimilarityParams(
6601                 viewer.isIncludeGappedColumns(), viewer.isMatchGaps(),
6602                 viewer.isIncludeGaps(),
6603                 viewer.isDenominateByShortestLength());
6604
6605         /*
6606          * create the panel (without computing the PCA)
6607          */
6608         PCAPanel panel = new PCAPanel(ap, modelName, params);
6609
6610         panel.setTitle(viewer.getTitle());
6611         panel.setBounds(new Rectangle(viewer.getXpos(), viewer.getYpos(),
6612                 viewer.getWidth(), viewer.getHeight()));
6613
6614         boolean showLabels = viewer.isShowLabels();
6615         panel.setShowLabels(showLabels);
6616         panel.getRotatableCanvas().setShowLabels(showLabels);
6617         panel.getRotatableCanvas()
6618                 .setBgColour(new Color(viewer.getBgColour()));
6619         panel.getRotatableCanvas()
6620                 .setApplyToAllViews(viewer.isLinkToAllViews());
6621
6622         /*
6623          * load PCA output data
6624          */
6625         ScoreModelI scoreModel = ScoreModels.getInstance()
6626                 .getScoreModel(modelName, ap);
6627         PCA pca = new PCA(null, scoreModel, params);
6628         PcaDataType pcaData = viewer.getPcaData();
6629
6630         MatrixI pairwise = loadDoubleMatrix(pcaData.getPairwiseMatrix());
6631         pca.setPairwiseScores(pairwise);
6632
6633         MatrixI triDiag = loadDoubleMatrix(pcaData.getTridiagonalMatrix());
6634         pca.setTridiagonal(triDiag);
6635
6636         MatrixI result = loadDoubleMatrix(pcaData.getEigenMatrix());
6637         pca.setEigenmatrix(result);
6638
6639         panel.getPcaModel().setPCA(pca);
6640
6641         /*
6642          * we haven't saved the input data! (JAL-2647 to do)
6643          */
6644         panel.setInputData(null);
6645
6646         /*
6647          * add the sequence points for the PCA display
6648          */
6649         List<jalview.datamodel.SequencePoint> seqPoints = new ArrayList<>();
6650         for (SequencePoint sp : viewer.getSequencePoint())
6651         {
6652           String seqId = sp.getSequenceRef();
6653           SequenceI seq = seqRefIds.get(seqId);
6654           if (seq == null)
6655           {
6656             throw new IllegalStateException(
6657                     "Unmatched seqref for PCA: " + seqId);
6658           }
6659           Point pt = new Point(sp.getXPos(), sp.getYPos(), sp.getZPos());
6660           jalview.datamodel.SequencePoint seqPoint = new jalview.datamodel.SequencePoint(
6661                   seq, pt);
6662           seqPoints.add(seqPoint);
6663         }
6664         panel.getRotatableCanvas().setPoints(seqPoints, seqPoints.size());
6665
6666         /*
6667          * set min-max ranges and scale after setPoints (which recomputes them)
6668          */
6669         panel.getRotatableCanvas().setScaleFactor(viewer.getScaleFactor());
6670         SeqPointMin spMin = viewer.getSeqPointMin();
6671         float[] min = new float[] { spMin.getXPos(), spMin.getYPos(),
6672             spMin.getZPos() };
6673         SeqPointMax spMax = viewer.getSeqPointMax();
6674         float[] max = new float[] { spMax.getXPos(), spMax.getYPos(),
6675             spMax.getZPos() };
6676         panel.getRotatableCanvas().setSeqMinMax(min, max);
6677
6678         // todo: hold points list in PCAModel only
6679         panel.getPcaModel().setSequencePoints(seqPoints);
6680
6681         panel.setSelectedDimensionIndex(viewer.getXDim(), X);
6682         panel.setSelectedDimensionIndex(viewer.getYDim(), Y);
6683         panel.setSelectedDimensionIndex(viewer.getZDim(), Z);
6684
6685         // is this duplication needed?
6686         panel.setTop(seqPoints.size() - 1);
6687         panel.getPcaModel().setTop(seqPoints.size() - 1);
6688
6689         /*
6690          * add the axes' end points for the display
6691          */
6692         for (int i = 0; i < 3; i++)
6693         {
6694           Axis axis = viewer.getAxis().get(i);
6695           panel.getRotatableCanvas().getAxisEndPoints()[i] = new Point(
6696                   axis.getXPos(), axis.getYPos(), axis.getZPos());
6697         }
6698
6699         Desktop.addInternalFrame(panel, MessageManager.formatMessage(
6700                 "label.calc_title", "PCA", modelName), 475, 450);
6701       }
6702     } catch (Exception ex)
6703     {
6704       Console.error("Error loading PCA: " + ex.toString());
6705     }
6706   }
6707
6708   private boolean isPasimap(PcaViewer viewer)
6709   {
6710     return viewer.getTitle().toLowerCase(Locale.ROOT)
6711     .startsWith("pasimap");
6712   }
6713   /**
6714    * Loads any saved PaSiMAp viewers using the function from PCA
6715    * 
6716    * @param jms
6717    * @param ap
6718    */
6719   protected void loadPaSiMapViewers(JalviewModel model, AlignmentPanel ap)
6720   {
6721     try
6722     {
6723       List<PcaViewer> pcaviewers = model.getPcaViewer();
6724       for (PcaViewer viewer : pcaviewers)
6725       {
6726         if (!isPasimap(viewer))
6727         {
6728           continue;
6729         }
6730         String modelName = viewer.getScoreModelName();
6731         /*
6732         SimilarityParamsI params = new SimilarityParams(
6733                 viewer.isIncludeGappedColumns(), viewer.isMatchGaps(),
6734                 viewer.isIncludeGaps(),
6735                 viewer.isDenominateByShortestLength());
6736         */
6737
6738         /*
6739          * create the panel (without computing the PaSiMAp)
6740          */
6741         PaSiMapPanel panel = new PaSiMapPanel(ap, modelName);
6742
6743         panel.setTitle(viewer.getTitle());
6744         panel.setBounds(new Rectangle(viewer.getXpos(), viewer.getYpos(),
6745                 viewer.getWidth(), viewer.getHeight()));
6746
6747         boolean showLabels = viewer.isShowLabels();
6748         panel.setShowLabels(showLabels);
6749         panel.getRotatableCanvas().setShowLabels(showLabels);
6750         panel.getRotatableCanvas()
6751                 .setBgColour(new Color(viewer.getBgColour()));
6752         panel.getRotatableCanvas()
6753                 .setApplyToAllViews(viewer.isLinkToAllViews());
6754
6755         /*
6756          * load PaSiMap output data
6757          */
6758         ScoreModelI scoreModel = ScoreModels.getInstance()
6759                 .getScoreModel(modelName, ap);
6760         PaSiMap pasimap = new PaSiMap(null, scoreModel, null);
6761         PcaDataType pasimapData = viewer.getPcaData();
6762
6763         MatrixI pairwise = loadDoubleMatrix(pasimapData.getPairwiseMatrix());
6764         pasimap.setPairwiseScores(pairwise);
6765
6766         MatrixI result = loadDoubleMatrix(pasimapData.getEigenMatrix());
6767         pasimap.setEigenmatrix(result);
6768
6769         panel.getPasimapModel().setPaSiMap(pasimap);
6770
6771         /*
6772          * we haven't saved the input data! (JAL-2647 to do)
6773          */
6774         panel.setInputData(null);
6775
6776         /*
6777          * add the sequence points for the PCA display
6778          */
6779         List<jalview.datamodel.SequencePoint> seqPoints = new ArrayList<>();
6780         for (SequencePoint sp : viewer.getSequencePoint())
6781         {
6782           String seqId = sp.getSequenceRef();
6783           SequenceI seq = seqRefIds.get(seqId);
6784           if (seq == null)
6785           {
6786             throw new IllegalStateException(
6787                     "Unmatched seqref for PaSiMap: " + seqId);
6788           }
6789           Point pt = new Point(sp.getXPos(), sp.getYPos(), sp.getZPos());
6790           jalview.datamodel.SequencePoint seqPoint = new jalview.datamodel.SequencePoint(
6791                   seq, pt);
6792           seqPoints.add(seqPoint);
6793         }
6794         panel.getRotatableCanvas().setPoints(seqPoints, seqPoints.size());
6795
6796         /*
6797          * set min-max ranges and scale after setPoints (which recomputes them)
6798          */
6799         panel.getRotatableCanvas().setScaleFactor(viewer.getScaleFactor());
6800         SeqPointMin spMin = viewer.getSeqPointMin();
6801         float[] min = new float[] { spMin.getXPos(), spMin.getYPos(),
6802             spMin.getZPos() };
6803         SeqPointMax spMax = viewer.getSeqPointMax();
6804         float[] max = new float[] { spMax.getXPos(), spMax.getYPos(),
6805             spMax.getZPos() };
6806         panel.getRotatableCanvas().setSeqMinMax(min, max);
6807
6808         // todo: hold points list in PCAModel only
6809         panel.getPasimapModel().setSequencePoints(seqPoints);
6810
6811         panel.setSelectedDimensionIndex(viewer.getXDim(), X);
6812         panel.setSelectedDimensionIndex(viewer.getYDim(), Y);
6813         panel.setSelectedDimensionIndex(viewer.getZDim(), Z);
6814
6815         // is this duplication needed?
6816         panel.setTop(seqPoints.size() - 1);
6817         panel.getPasimapModel().setTop(seqPoints.size() - 1);
6818
6819         /*
6820          * add the axes' end points for the display
6821          */
6822         for (int i = 0; i < 3; i++)
6823         {
6824           Axis axis = viewer.getAxis().get(i);
6825           panel.getRotatableCanvas().getAxisEndPoints()[i] = new Point(
6826                   axis.getXPos(), axis.getYPos(), axis.getZPos());
6827         }
6828
6829         Desktop.addInternalFrame(panel, MessageManager.formatMessage(
6830                 "label.calc_title", "PaSiMap", modelName), 475, 450);
6831       }
6832     } catch (Exception ex)
6833     {
6834       Console.error("Error loading PaSiMap: " + ex.toString());
6835     }
6836   }
6837
6838   /**
6839    * Creates a new structure viewer window
6840    * 
6841    * @param viewerType
6842    * @param viewerData
6843    * @param af
6844    * @param jprovider
6845    */
6846   protected void createStructureViewer(ViewerType viewerType,
6847           final Entry<String, StructureViewerModel> viewerData,
6848           AlignFrame af, jarInputStreamProvider jprovider)
6849   {
6850     final StructureViewerModel viewerModel = viewerData.getValue();
6851     String sessionFilePath = null;
6852
6853     if (viewerType == ViewerType.JMOL)
6854     {
6855       sessionFilePath = rewriteJmolSession(viewerModel, jprovider);
6856     }
6857     else
6858     {
6859       String viewerJarEntryName = getViewerJarEntryName(
6860               viewerModel.getViewId());
6861       sessionFilePath = copyJarEntry(jprovider, viewerJarEntryName,
6862               "viewerSession", ".tmp");
6863     }
6864     final String sessionPath = sessionFilePath;
6865     final String sviewid = viewerData.getKey();
6866     try
6867     {
6868       SwingUtilities.invokeAndWait(new Runnable()
6869       {
6870         @Override
6871         public void run()
6872         {
6873           JalviewStructureDisplayI sview = null;
6874           try
6875           {
6876             sview = StructureViewer.createView(viewerType, af.alignPanel,
6877                     viewerModel, sessionPath, sviewid);
6878             addNewStructureViewer(sview);
6879           } catch (OutOfMemoryError ex)
6880           {
6881             new OOMWarning("Restoring structure view for " + viewerType,
6882                     (OutOfMemoryError) ex.getCause());
6883             if (sview != null && sview.isVisible())
6884             {
6885               sview.closeViewer(false);
6886               sview.setVisible(false);
6887               sview.dispose();
6888             }
6889           }
6890         }
6891       });
6892     } catch (InvocationTargetException | InterruptedException ex)
6893     {
6894       Console.warn("Unexpected error when opening " + viewerType
6895               + " structure viewer", ex);
6896     }
6897   }
6898
6899   /**
6900    * Rewrites a Jmol session script, saves it to a temporary file, and returns
6901    * the path of the file. "load file" commands are rewritten to change the
6902    * original PDB file names to those created as the Jalview project is loaded.
6903    * 
6904    * @param svattrib
6905    * @param jprovider
6906    * @return
6907    */
6908   private String rewriteJmolSession(StructureViewerModel svattrib,
6909           jarInputStreamProvider jprovider)
6910   {
6911     String state = svattrib.getStateData(); // Jalview < 2.9
6912     if (state == null || state.isEmpty()) // Jalview >= 2.9
6913     {
6914       String jarEntryName = getViewerJarEntryName(svattrib.getViewId());
6915       state = readJarEntry(jprovider, jarEntryName);
6916     }
6917     // TODO or simpler? for each key in oldFiles,
6918     // replace key.getPath() in state with oldFiles.get(key).getFilePath()
6919     // (allowing for different path escapings)
6920     StringBuilder rewritten = new StringBuilder(state.length());
6921     int cp = 0, ncp, ecp;
6922     Map<File, StructureData> oldFiles = svattrib.getFileData();
6923     while ((ncp = state.indexOf("load ", cp)) > -1)
6924     {
6925       do
6926       {
6927         // look for next filename in load statement
6928         rewritten.append(state.substring(cp,
6929                 ncp = (state.indexOf("\"", ncp + 1) + 1)));
6930         String oldfilenam = state.substring(ncp,
6931                 ecp = state.indexOf("\"", ncp));
6932         // recover the new mapping data for this old filename
6933         // have to normalize filename - since Jmol and jalview do
6934         // filename translation differently.
6935         StructureData filedat = oldFiles.get(new File(oldfilenam));
6936         if (filedat == null)
6937         {
6938           String reformatedOldFilename = oldfilenam.replaceAll("/", "\\\\");
6939           filedat = oldFiles.get(new File(reformatedOldFilename));
6940         }
6941         rewritten.append(Platform.escapeBackslashes(filedat.getFilePath()));
6942         rewritten.append("\"");
6943         cp = ecp + 1; // advance beyond last \" and set cursor so we can
6944                       // look for next file statement.
6945       } while ((ncp = state.indexOf("/*file*/", cp)) > -1);
6946     }
6947     if (cp > 0)
6948     {
6949       // just append rest of state
6950       rewritten.append(state.substring(cp));
6951     }
6952     else
6953     {
6954       System.err.print("Ignoring incomplete Jmol state for PDB ids: ");
6955       rewritten = new StringBuilder(state);
6956       rewritten.append("; load append ");
6957       for (File id : oldFiles.keySet())
6958       {
6959         // add pdb files that should be present in the viewer
6960         StructureData filedat = oldFiles.get(id);
6961         rewritten.append(" \"").append(filedat.getFilePath()).append("\"");
6962       }
6963       rewritten.append(";");
6964     }
6965
6966     if (rewritten.length() == 0)
6967     {
6968       return null;
6969     }
6970     final String history = "history = ";
6971     int historyIndex = rewritten.indexOf(history);
6972     if (historyIndex > -1)
6973     {
6974       /*
6975        * change "history = [true|false];" to "history = [1|0];"
6976        */
6977       historyIndex += history.length();
6978       String val = rewritten.substring(historyIndex, historyIndex + 5);
6979       if (val.startsWith("true"))
6980       {
6981         rewritten.replace(historyIndex, historyIndex + 4, "1");
6982       }
6983       else if (val.startsWith("false"))
6984       {
6985         rewritten.replace(historyIndex, historyIndex + 5, "0");
6986       }
6987     }
6988
6989     try
6990     {
6991       File tmp = File.createTempFile("viewerSession", ".tmp");
6992       try (OutputStream os = new FileOutputStream(tmp))
6993       {
6994         InputStream is = new ByteArrayInputStream(
6995                 rewritten.toString().getBytes());
6996         copyAll(is, os);
6997         return tmp.getAbsolutePath();
6998       }
6999     } catch (IOException e)
7000     {
7001       Console.error("Error restoring Jmol session: " + e.toString());
7002     }
7003     return null;
7004   }
7005
7006   /**
7007    * Populates an XML model of the feature colour scheme for one feature type
7008    * 
7009    * @param featureType
7010    * @param fcol
7011    * @return
7012    */
7013   public static Colour marshalColour(String featureType,
7014           FeatureColourI fcol)
7015   {
7016     Colour col = new Colour();
7017     if (fcol.isSimpleColour())
7018     {
7019       col.setRGB(Format.getHexString(fcol.getColour()));
7020     }
7021     else
7022     {
7023       col.setRGB(Format.getHexString(fcol.getMaxColour()));
7024       col.setMin(fcol.getMin());
7025       col.setMax(fcol.getMax());
7026       col.setMinRGB(jalview.util.Format.getHexString(fcol.getMinColour()));
7027       col.setAutoScale(fcol.isAutoScaled());
7028       col.setThreshold(fcol.getThreshold());
7029       col.setColourByLabel(fcol.isColourByLabel());
7030       col.setThreshType(fcol.isAboveThreshold() ? ThresholdType.ABOVE
7031               : (fcol.isBelowThreshold() ? ThresholdType.BELOW
7032                       : ThresholdType.NONE));
7033       if (fcol.isColourByAttribute())
7034       {
7035         final String[] attName = fcol.getAttributeName();
7036         col.getAttributeName().add(attName[0]);
7037         if (attName.length > 1)
7038         {
7039           col.getAttributeName().add(attName[1]);
7040         }
7041       }
7042       Color noColour = fcol.getNoColour();
7043       if (noColour == null)
7044       {
7045         col.setNoValueColour(NoValueColour.NONE);
7046       }
7047       else if (noColour == fcol.getMaxColour())
7048       {
7049         col.setNoValueColour(NoValueColour.MAX);
7050       }
7051       else
7052       {
7053         col.setNoValueColour(NoValueColour.MIN);
7054       }
7055     }
7056     col.setName(featureType);
7057     return col;
7058   }
7059
7060   /**
7061    * Populates an XML model of the feature filter(s) for one feature type
7062    * 
7063    * @param firstMatcher
7064    *          the first (or only) match condition)
7065    * @param filter
7066    *          remaining match conditions (if any)
7067    * @param and
7068    *          if true, conditions are and-ed, else or-ed
7069    */
7070   public static jalview.xml.binding.jalview.FeatureMatcherSet marshalFilter(
7071           FeatureMatcherI firstMatcher, Iterator<FeatureMatcherI> filters,
7072           boolean and)
7073   {
7074     jalview.xml.binding.jalview.FeatureMatcherSet result = new jalview.xml.binding.jalview.FeatureMatcherSet();
7075
7076     if (filters.hasNext())
7077     {
7078       /*
7079        * compound matcher
7080        */
7081       CompoundMatcher compound = new CompoundMatcher();
7082       compound.setAnd(and);
7083       jalview.xml.binding.jalview.FeatureMatcherSet matcher1 = marshalFilter(
7084               firstMatcher, Collections.emptyIterator(), and);
7085       // compound.addMatcherSet(matcher1);
7086       compound.getMatcherSet().add(matcher1);
7087       FeatureMatcherI nextMatcher = filters.next();
7088       jalview.xml.binding.jalview.FeatureMatcherSet matcher2 = marshalFilter(
7089               nextMatcher, filters, and);
7090       // compound.addMatcherSet(matcher2);
7091       compound.getMatcherSet().add(matcher2);
7092       result.setCompoundMatcher(compound);
7093     }
7094     else
7095     {
7096       /*
7097        * single condition matcher
7098        */
7099       // MatchCondition matcherModel = new MatchCondition();
7100       jalview.xml.binding.jalview.FeatureMatcher matcherModel = new jalview.xml.binding.jalview.FeatureMatcher();
7101       matcherModel.setCondition(
7102               firstMatcher.getMatcher().getCondition().getStableName());
7103       matcherModel.setValue(firstMatcher.getMatcher().getPattern());
7104       if (firstMatcher.isByAttribute())
7105       {
7106         matcherModel.setBy(FilterBy.BY_ATTRIBUTE);
7107         // matcherModel.setAttributeName(firstMatcher.getAttribute());
7108         String[] attName = firstMatcher.getAttribute();
7109         matcherModel.getAttributeName().add(attName[0]); // attribute
7110         if (attName.length > 1)
7111         {
7112           matcherModel.getAttributeName().add(attName[1]); // sub-attribute
7113         }
7114       }
7115       else if (firstMatcher.isByLabel())
7116       {
7117         matcherModel.setBy(FilterBy.BY_LABEL);
7118       }
7119       else if (firstMatcher.isByScore())
7120       {
7121         matcherModel.setBy(FilterBy.BY_SCORE);
7122       }
7123       result.setMatchCondition(matcherModel);
7124     }
7125
7126     return result;
7127   }
7128
7129   /**
7130    * Loads one XML model of a feature filter to a Jalview object
7131    * 
7132    * @param featureType
7133    * @param matcherSetModel
7134    * @return
7135    */
7136   public static FeatureMatcherSetI parseFilter(String featureType,
7137           jalview.xml.binding.jalview.FeatureMatcherSet matcherSetModel)
7138   {
7139     FeatureMatcherSetI result = new FeatureMatcherSet();
7140     try
7141     {
7142       parseFilterConditions(result, matcherSetModel, true);
7143     } catch (IllegalStateException e)
7144     {
7145       // mixing AND and OR conditions perhaps
7146       jalview.bin.Console.errPrintln(
7147               String.format("Error reading filter conditions for '%s': %s",
7148                       featureType, e.getMessage()));
7149       // return as much as was parsed up to the error
7150     }
7151
7152     return result;
7153   }
7154
7155   /**
7156    * Adds feature match conditions to matcherSet as unmarshalled from XML
7157    * (possibly recursively for compound conditions)
7158    * 
7159    * @param matcherSet
7160    * @param matcherSetModel
7161    * @param and
7162    *          if true, multiple conditions are AND-ed, else they are OR-ed
7163    * @throws IllegalStateException
7164    *           if AND and OR conditions are mixed
7165    */
7166   protected static void parseFilterConditions(FeatureMatcherSetI matcherSet,
7167           jalview.xml.binding.jalview.FeatureMatcherSet matcherSetModel,
7168           boolean and)
7169   {
7170     jalview.xml.binding.jalview.FeatureMatcher mc = matcherSetModel
7171             .getMatchCondition();
7172     if (mc != null)
7173     {
7174       /*
7175        * single condition
7176        */
7177       FilterBy filterBy = mc.getBy();
7178       Condition cond = Condition.fromString(mc.getCondition());
7179       String pattern = mc.getValue();
7180       FeatureMatcherI matchCondition = null;
7181       if (filterBy == FilterBy.BY_LABEL)
7182       {
7183         matchCondition = FeatureMatcher.byLabel(cond, pattern);
7184       }
7185       else if (filterBy == FilterBy.BY_SCORE)
7186       {
7187         matchCondition = FeatureMatcher.byScore(cond, pattern);
7188
7189       }
7190       else if (filterBy == FilterBy.BY_ATTRIBUTE)
7191       {
7192         final List<String> attributeName = mc.getAttributeName();
7193         String[] attNames = attributeName
7194                 .toArray(new String[attributeName.size()]);
7195         matchCondition = FeatureMatcher.byAttribute(cond, pattern,
7196                 attNames);
7197       }
7198
7199       /*
7200        * note this throws IllegalStateException if AND-ing to a 
7201        * previously OR-ed compound condition, or vice versa
7202        */
7203       if (and)
7204       {
7205         matcherSet.and(matchCondition);
7206       }
7207       else
7208       {
7209         matcherSet.or(matchCondition);
7210       }
7211     }
7212     else
7213     {
7214       /*
7215        * compound condition
7216        */
7217       List<jalview.xml.binding.jalview.FeatureMatcherSet> matchers = matcherSetModel
7218               .getCompoundMatcher().getMatcherSet();
7219       boolean anded = matcherSetModel.getCompoundMatcher().isAnd();
7220       if (matchers.size() == 2)
7221       {
7222         parseFilterConditions(matcherSet, matchers.get(0), anded);
7223         parseFilterConditions(matcherSet, matchers.get(1), anded);
7224       }
7225       else
7226       {
7227         jalview.bin.Console
7228                 .errPrintln("Malformed compound filter condition");
7229       }
7230     }
7231   }
7232
7233   /**
7234    * Loads one XML model of a feature colour to a Jalview object
7235    * 
7236    * @param colourModel
7237    * @return
7238    */
7239   public static FeatureColourI parseColour(Colour colourModel)
7240   {
7241     FeatureColourI colour = null;
7242
7243     if (colourModel.getMax() != null)
7244     {
7245       Color mincol = null;
7246       Color maxcol = null;
7247       Color noValueColour = null;
7248
7249       try
7250       {
7251         mincol = new Color(Integer.parseInt(colourModel.getMinRGB(), 16));
7252         maxcol = new Color(Integer.parseInt(colourModel.getRGB(), 16));
7253       } catch (Exception e)
7254       {
7255         Console.warn("Couldn't parse out graduated feature color.", e);
7256       }
7257
7258       NoValueColour noCol = colourModel.getNoValueColour();
7259       if (noCol == NoValueColour.MIN)
7260       {
7261         noValueColour = mincol;
7262       }
7263       else if (noCol == NoValueColour.MAX)
7264       {
7265         noValueColour = maxcol;
7266       }
7267
7268       colour = new FeatureColour(maxcol, mincol, maxcol, noValueColour,
7269               safeFloat(colourModel.getMin()),
7270               safeFloat(colourModel.getMax()));
7271       final List<String> attributeName = colourModel.getAttributeName();
7272       String[] attributes = attributeName
7273               .toArray(new String[attributeName.size()]);
7274       if (attributes != null && attributes.length > 0)
7275       {
7276         colour.setAttributeName(attributes);
7277       }
7278       if (colourModel.isAutoScale() != null)
7279       {
7280         colour.setAutoScaled(colourModel.isAutoScale().booleanValue());
7281       }
7282       if (colourModel.isColourByLabel() != null)
7283       {
7284         colour.setColourByLabel(
7285                 colourModel.isColourByLabel().booleanValue());
7286       }
7287       if (colourModel.getThreshold() != null)
7288       {
7289         colour.setThreshold(colourModel.getThreshold().floatValue());
7290       }
7291       ThresholdType ttyp = colourModel.getThreshType();
7292       if (ttyp == ThresholdType.ABOVE)
7293       {
7294         colour.setAboveThreshold(true);
7295       }
7296       else if (ttyp == ThresholdType.BELOW)
7297       {
7298         colour.setBelowThreshold(true);
7299       }
7300     }
7301     else
7302     {
7303       Color color = new Color(Integer.parseInt(colourModel.getRGB(), 16));
7304       colour = new FeatureColour(color);
7305     }
7306
7307     return colour;
7308   }
7309
7310   public static void setStateSavedUpToDate(boolean s)
7311   {
7312     Console.debug("Setting overall stateSavedUpToDate to " + s);
7313     stateSavedUpToDate = s;
7314   }
7315
7316   public static boolean stateSavedUpToDate()
7317   {
7318     Console.debug("Returning overall stateSavedUpToDate value: "
7319             + stateSavedUpToDate);
7320     return stateSavedUpToDate;
7321   }
7322
7323   public static boolean allSavedUpToDate()
7324   {
7325     if (stateSavedUpToDate()) // nothing happened since last project save
7326       return true;
7327
7328     AlignFrame[] frames = Desktop.getDesktopAlignFrames();
7329     if (frames != null)
7330     {
7331       for (int i = 0; i < frames.length; i++)
7332       {
7333         if (frames[i] == null)
7334           continue;
7335         if (!frames[i].getViewport().savedUpToDate())
7336           return false; // at least one alignment is not individually saved
7337       }
7338     }
7339     return true;
7340   }
7341
7342   // used for debugging and tests
7343   private static int debugDelaySave = 20;
7344
7345   public static void setDebugDelaySave(int n)
7346   {
7347     debugDelaySave = n;
7348   }
7349 }