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