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