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