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