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