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