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