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