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