JAL-3446 from JAL-3253-applet Jalview2XML integration (retains
[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 (tree.getId() != null)
4330           {
4331             // perhaps bind the tree id to something ?
4332           }
4333         }
4334         else
4335         {
4336           // update local tree attributes ?
4337           // TODO: should check if tp has been manipulated by user - if so its
4338           // settings shouldn't be modified
4339           tp.setTitle(tree.getTitle());
4340           tp.setBounds(new Rectangle(safeInt(tree.getXpos()),
4341                   safeInt(tree.getYpos()), safeInt(tree.getWidth()),
4342                   safeInt(tree.getHeight())));
4343           tp.setViewport(av); // af.viewport;
4344           // TODO: verify 'associate with all views' works still
4345           tp.getTreeCanvas().setViewport(av); // af.viewport;
4346           tp.getTreeCanvas().setAssociatedPanel(ap); // af.alignPanel;
4347         }
4348         tp.getTreeCanvas().setApplyToAllViews(tree.isLinkToAllViews());
4349         if (tp == null)
4350         {
4351           warn("There was a problem recovering stored Newick tree: \n"
4352                   + tree.getNewick());
4353           continue;
4354         }
4355
4356         tp.fitToWindow.setState(safeBoolean(tree.isFitToWindow()));
4357         tp.fitToWindow_actionPerformed(null);
4358
4359         if (tree.getFontName() != null)
4360         {
4361           tp.setTreeFont(
4362                   new Font(tree.getFontName(), safeInt(tree.getFontStyle()),
4363                           safeInt(tree.getFontSize())));
4364         }
4365         else
4366         {
4367           tp.setTreeFont(
4368                   new Font(view.getFontName(), safeInt(view.getFontStyle()),
4369                           safeInt(view.getFontSize())));
4370         }
4371
4372         tp.showPlaceholders(safeBoolean(tree.isMarkUnlinked()));
4373         tp.showBootstrap(safeBoolean(tree.isShowBootstrap()));
4374         tp.showDistances(safeBoolean(tree.isShowDistances()));
4375
4376         tp.getTreeCanvas().setThreshold(safeFloat(tree.getThreshold()));
4377
4378         if (safeBoolean(tree.isCurrentTree()))
4379         {
4380           af.getViewport().setCurrentTree(tp.getTree());
4381         }
4382       }
4383
4384     } catch (Exception ex)
4385     {
4386       ex.printStackTrace();
4387     }
4388   }
4389
4390   /**
4391    * Load and link any saved structure viewers.
4392    * 
4393    * @param jprovider
4394    * @param jseqs
4395    * @param af
4396    * @param ap
4397    */
4398   protected void loadPDBStructures(jarInputStreamProvider jprovider,
4399           List<JSeq> jseqs, AlignFrame af, AlignmentPanel ap)
4400   {
4401     /*
4402      * Run through all PDB ids on the alignment, and collect mappings between
4403      * distinct view ids and all sequences referring to that view.
4404      */
4405     Map<String, StructureViewerModel> structureViewers = new LinkedHashMap<>();
4406
4407     for (int i = 0; i < jseqs.size(); i++)
4408     {
4409       JSeq jseq = jseqs.get(i);
4410       if (jseq.getPdbids().size() > 0)
4411       {
4412         List<Pdbids> ids = jseq.getPdbids();
4413         for (int p = 0; p < ids.size(); p++)
4414         {
4415           Pdbids pdbid = ids.get(p);
4416           final int structureStateCount = pdbid.getStructureState().size();
4417           for (int s = 0; s < structureStateCount; s++)
4418           {
4419             // check to see if we haven't already created this structure view
4420             final StructureState structureState = pdbid
4421                     .getStructureState().get(s);
4422             String sviewid = (structureState.getViewId() == null) ? null
4423                     : structureState.getViewId() + uniqueSetSuffix;
4424             jalview.datamodel.PDBEntry jpdb = new jalview.datamodel.PDBEntry();
4425             // Originally : pdbid.getFile()
4426             // : TODO: verify external PDB file recovery still works in normal
4427             // jalview project load
4428             jpdb.setFile(
4429                     loadPDBFile(jprovider, pdbid.getId(), pdbid.getFile()));
4430             jpdb.setId(pdbid.getId());
4431
4432             int x = safeInt(structureState.getXpos());
4433             int y = safeInt(structureState.getYpos());
4434             int width = safeInt(structureState.getWidth());
4435             int height = safeInt(structureState.getHeight());
4436
4437             // Probably don't need to do this anymore...
4438             // Desktop.getDesktop().getComponentAt(x, y);
4439             // TODO: NOW: check that this recovers the PDB file correctly.
4440             String pdbFile = loadPDBFile(jprovider, pdbid.getId(),
4441                     pdbid.getFile());
4442             jalview.datamodel.SequenceI seq = seqRefIds
4443                     .get(jseq.getId() + "");
4444             if (sviewid == null)
4445             {
4446               sviewid = "_jalview_pre2_4_" + x + "," + y + "," + width + ","
4447                       + height;
4448             }
4449             if (!structureViewers.containsKey(sviewid))
4450             {
4451               structureViewers.put(sviewid,
4452                       new StructureViewerModel(x, y, width, height, false,
4453                               false, true, structureState.getViewId(),
4454                               structureState.getType()));
4455               // Legacy pre-2.7 conversion JAL-823 :
4456               // do not assume any view has to be linked for colour by
4457               // sequence
4458             }
4459
4460             // assemble String[] { pdb files }, String[] { id for each
4461             // file }, orig_fileloc, SequenceI[][] {{ seqs_file 1 }, {
4462             // seqs_file 2}, boolean[] {
4463             // linkAlignPanel,superposeWithAlignpanel}} from hash
4464             StructureViewerModel jmoldat = structureViewers.get(sviewid);
4465             jmoldat.setAlignWithPanel(jmoldat.isAlignWithPanel()
4466                     || structureState.isAlignwithAlignPanel());
4467
4468             /*
4469              * Default colour by linked panel to false if not specified (e.g.
4470              * for pre-2.7 projects)
4471              */
4472             boolean colourWithAlignPanel = jmoldat.isColourWithAlignPanel();
4473             colourWithAlignPanel |= structureState.isColourwithAlignPanel();
4474             jmoldat.setColourWithAlignPanel(colourWithAlignPanel);
4475
4476             /*
4477              * Default colour by viewer to true if not specified (e.g. for
4478              * pre-2.7 projects)
4479              */
4480             boolean colourByViewer = jmoldat.isColourByViewer();
4481             colourByViewer &= structureState.isColourByJmol();
4482             jmoldat.setColourByViewer(colourByViewer);
4483
4484             if (jmoldat.getStateData().length() < structureState
4485                     .getValue()/*Content()*/.length())
4486             {
4487               jmoldat.setStateData(structureState.getValue());// Content());
4488             }
4489             if (pdbid.getFile() != null)
4490             {
4491               File mapkey = new File(pdbid.getFile());
4492               StructureData seqstrmaps = jmoldat.getFileData().get(mapkey);
4493               if (seqstrmaps == null)
4494               {
4495                 jmoldat.getFileData().put(mapkey,
4496                         seqstrmaps = jmoldat.new StructureData(pdbFile,
4497                                 pdbid.getId()));
4498               }
4499               if (!seqstrmaps.getSeqList().contains(seq))
4500               {
4501                 seqstrmaps.getSeqList().add(seq);
4502                 // TODO and chains?
4503               }
4504             }
4505             else
4506             {
4507               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");
4508               warn(errorMessage);
4509             }
4510           }
4511         }
4512       }
4513     }
4514     // Instantiate the associated structure views
4515     for (Entry<String, StructureViewerModel> entry : structureViewers
4516             .entrySet())
4517     {
4518       try
4519       {
4520         createOrLinkStructureViewer(entry, af, ap, jprovider);
4521       } catch (Exception e)
4522       {
4523         System.err.println(
4524                 "Error loading structure viewer: " + e.getMessage());
4525         // failed - try the next one
4526       }
4527     }
4528   }
4529
4530   /**
4531    * 
4532    * @param viewerData
4533    * @param af
4534    * @param ap
4535    * @param jprovider
4536    */
4537   protected void createOrLinkStructureViewer(
4538           Entry<String, StructureViewerModel> viewerData, AlignFrame af,
4539           AlignmentPanel ap, jarInputStreamProvider jprovider)
4540   {
4541     final StructureViewerModel stateData = viewerData.getValue();
4542
4543     /*
4544      * Search for any viewer windows already open from other alignment views
4545      * that exactly match the stored structure state
4546      */
4547     StructureViewerBase comp = findMatchingViewer(viewerData);
4548
4549     if (comp != null)
4550     {
4551       linkStructureViewer(ap, comp, stateData);
4552       return;
4553     }
4554
4555     /*
4556      * From 2.9: stateData.type contains JMOL or CHIMERA, data is in jar entry
4557      * "viewer_"+stateData.viewId
4558      */
4559     if (ViewerType.CHIMERA.toString().equals(stateData.getType()))
4560     {
4561       createChimeraViewer(viewerData, af, jprovider);
4562     }
4563     else
4564     {
4565       /*
4566        * else Jmol (if pre-2.9, stateData contains JMOL state string)
4567        */
4568       createJmolViewer(viewerData, af, jprovider);
4569     }
4570   }
4571
4572   /**
4573    * Create a new Chimera viewer.
4574    * 
4575    * @param data
4576    * @param af
4577    * @param jprovider
4578    */
4579   protected void createChimeraViewer(
4580           Entry<String, StructureViewerModel> viewerData, AlignFrame af,
4581           jarInputStreamProvider jprovider)
4582   {
4583     StructureViewerModel data = viewerData.getValue();
4584     String chimeraSessionFile = data.getStateData();
4585
4586     /*
4587      * Copy Chimera session from jar entry "viewer_"+viewId to a temporary file
4588      * 
4589      * NB this is the 'saved' viewId as in the project file XML, _not_ the
4590      * 'uniquified' sviewid used to reconstruct the viewer here
4591      */
4592     String viewerJarEntryName = getViewerJarEntryName(data.getViewId());
4593     chimeraSessionFile = copyJarEntry(jprovider, viewerJarEntryName,
4594             "chimera", ".py");
4595
4596     Set<Entry<File, StructureData>> fileData = data.getFileData()
4597             .entrySet();
4598     List<PDBEntry> pdbs = new ArrayList<>();
4599     List<SequenceI[]> allseqs = new ArrayList<>();
4600     for (Entry<File, StructureData> pdb : fileData)
4601     {
4602       String filePath = pdb.getValue().getFilePath();
4603       String pdbId = pdb.getValue().getPdbId();
4604       // pdbs.add(new PDBEntry(filePath, pdbId));
4605       pdbs.add(new PDBEntry(pdbId, null, PDBEntry.Type.PDB, filePath));
4606       final List<SequenceI> seqList = pdb.getValue().getSeqList();
4607       SequenceI[] seqs = seqList.toArray(new SequenceI[seqList.size()]);
4608       allseqs.add(seqs);
4609     }
4610
4611     boolean colourByChimera = data.isColourByViewer();
4612     boolean colourBySequence = data.isColourWithAlignPanel();
4613
4614     // TODO use StructureViewer as a factory here, see JAL-1761
4615     final PDBEntry[] pdbArray = pdbs.toArray(new PDBEntry[pdbs.size()]);
4616     final SequenceI[][] seqsArray = allseqs
4617             .toArray(new SequenceI[allseqs.size()][]);
4618     String newViewId = viewerData.getKey();
4619
4620     ChimeraViewFrame cvf = new ChimeraViewFrame(chimeraSessionFile,
4621             af.alignPanel, pdbArray, seqsArray, colourByChimera,
4622             colourBySequence, newViewId);
4623     cvf.setSize(data.getWidth(), data.getHeight());
4624     cvf.setLocation(data.getX(), data.getY());
4625   }
4626
4627   /**
4628    * Create a new Jmol window. First parse the Jmol state to translate filenames
4629    * loaded into the view, and record the order in which files are shown in the
4630    * Jmol view, so we can add the sequence mappings in same order.
4631    * 
4632    * @param viewerData
4633    * @param af
4634    * @param jprovider
4635    */
4636   protected void createJmolViewer(
4637           final Entry<String, StructureViewerModel> viewerData,
4638           AlignFrame af, jarInputStreamProvider jprovider)
4639   {
4640     final StructureViewerModel svattrib = viewerData.getValue();
4641     String state = svattrib.getStateData();
4642
4643     /*
4644      * Pre-2.9: state element value is the Jmol state string
4645      * 
4646      * 2.9+: @type is "JMOL", state data is in a Jar file member named "viewer_"
4647      * + viewId
4648      */
4649     if (ViewerType.JMOL.toString().equals(svattrib.getType()))
4650     {
4651       state = readJarEntry(jprovider,
4652               getViewerJarEntryName(svattrib.getViewId()));
4653     }
4654
4655     List<String> pdbfilenames = new ArrayList<>();
4656     List<SequenceI[]> seqmaps = new ArrayList<>();
4657     List<String> pdbids = new ArrayList<>();
4658     StringBuilder newFileLoc = new StringBuilder(64);
4659     int cp = 0, ncp, ecp;
4660     Map<File, StructureData> oldFiles = svattrib.getFileData();
4661     while ((ncp = state.indexOf("load ", cp)) > -1)
4662     {
4663       do
4664       {
4665         // look for next filename in load statement
4666         newFileLoc.append(state.substring(cp,
4667                 ncp = (state.indexOf("\"", ncp + 1) + 1)));
4668         String oldfilenam = state.substring(ncp,
4669                 ecp = state.indexOf("\"", ncp));
4670         // recover the new mapping data for this old filename
4671         // have to normalize filename - since Jmol and jalview do
4672         // filename
4673         // translation differently.
4674         StructureData filedat = oldFiles.get(new File(oldfilenam));
4675         if (filedat == null)
4676         {
4677           String reformatedOldFilename = oldfilenam.replaceAll("/", "\\\\");
4678           filedat = oldFiles.get(new File(reformatedOldFilename));
4679         }
4680         newFileLoc
4681                 .append(Platform.escapeBackslashes(filedat.getFilePath()));
4682         pdbfilenames.add(filedat.getFilePath());
4683         pdbids.add(filedat.getPdbId());
4684         seqmaps.add(filedat.getSeqList().toArray(new SequenceI[0]));
4685         newFileLoc.append("\"");
4686         cp = ecp + 1; // advance beyond last \" and set cursor so we can
4687                       // look for next file statement.
4688       } while ((ncp = state.indexOf("/*file*/", cp)) > -1);
4689     }
4690     if (cp > 0)
4691     {
4692       // just append rest of state
4693       newFileLoc.append(state.substring(cp));
4694     }
4695     else
4696     {
4697       System.err.print("Ignoring incomplete Jmol state for PDB ids: ");
4698       newFileLoc = new StringBuilder(state);
4699       newFileLoc.append("; load append ");
4700       for (File id : oldFiles.keySet())
4701       {
4702         // add this and any other pdb files that should be present in
4703         // the viewer
4704         StructureData filedat = oldFiles.get(id);
4705         newFileLoc.append(filedat.getFilePath());
4706         pdbfilenames.add(filedat.getFilePath());
4707         pdbids.add(filedat.getPdbId());
4708         seqmaps.add(filedat.getSeqList().toArray(new SequenceI[0]));
4709         newFileLoc.append(" \"");
4710         newFileLoc.append(filedat.getFilePath());
4711         newFileLoc.append("\"");
4712
4713       }
4714       newFileLoc.append(";");
4715     }
4716
4717     if (newFileLoc.length() == 0)
4718     {
4719       return;
4720     }
4721     int histbug = newFileLoc.indexOf("history = ");
4722     if (histbug > -1)
4723     {
4724       /*
4725        * change "history = [true|false];" to "history = [1|0];"
4726        */
4727       histbug += 10;
4728       int diff = histbug == -1 ? -1 : newFileLoc.indexOf(";", histbug);
4729       String val = (diff == -1) ? null
4730               : newFileLoc.substring(histbug, diff);
4731       if (val != null && val.length() >= 4)
4732       {
4733         if (val.contains("e")) // eh? what can it be?
4734         {
4735           if (val.trim().equals("true"))
4736           {
4737             val = "1";
4738           }
4739           else
4740           {
4741             val = "0";
4742           }
4743           newFileLoc.replace(histbug, diff, val);
4744         }
4745       }
4746     }
4747
4748     final String[] pdbf = pdbfilenames
4749             .toArray(new String[pdbfilenames.size()]);
4750     final String[] id = pdbids.toArray(new String[pdbids.size()]);
4751     final SequenceI[][] sq = seqmaps
4752             .toArray(new SequenceI[seqmaps.size()][]);
4753     final String fileloc = newFileLoc.toString();
4754     final String sviewid = viewerData.getKey();
4755     final AlignFrame alf = af;
4756     final Rectangle rect = new Rectangle(svattrib.getX(), svattrib.getY(),
4757             svattrib.getWidth(), svattrib.getHeight());
4758     
4759     // BH again was invokeAndWait
4760     // try
4761     // {
4762       javax.swing.SwingUtilities.invokeLater(new Runnable()
4763       {
4764         @Override
4765         public void run()
4766         {
4767           JalviewStructureDisplayI sview = null;
4768           try
4769           {
4770             sview = new StructureViewer(
4771                     alf.alignPanel.getStructureSelectionManager())
4772                             .createView(StructureViewer.ViewerType.JMOL,
4773                                     pdbf, id, sq, alf.alignPanel, svattrib,
4774                                     fileloc, rect, sviewid);
4775             addNewStructureViewer(sview);
4776           } catch (OutOfMemoryError ex)
4777           {
4778             new OOMWarning("restoring structure view for PDB id " + id,
4779                     (OutOfMemoryError) ex.getCause());
4780             if (sview != null && sview.isVisible())
4781             {
4782               sview.closeViewer(false);
4783               sview.setVisible(false);
4784               sview.dispose();
4785             }
4786           }
4787         }
4788       });
4789     // } catch (InvocationTargetException ex)
4790     // {
4791     // warn("Unexpected error when opening Jmol view.", ex);
4792     //
4793     // } catch (InterruptedException e)
4794     // {
4795     // // e.printStackTrace();
4796     // }
4797
4798   }
4799
4800   /**
4801    * Generates a name for the entry in the project jar file to hold state
4802    * information for a structure viewer
4803    * 
4804    * @param viewId
4805    * @return
4806    */
4807   protected String getViewerJarEntryName(String viewId)
4808   {
4809     return VIEWER_PREFIX + viewId;
4810   }
4811
4812   /**
4813    * Returns any open frame that matches given structure viewer data. The match
4814    * is based on the unique viewId, or (for older project versions) the frame's
4815    * geometry.
4816    * 
4817    * @param viewerData
4818    * @return
4819    */
4820   protected StructureViewerBase findMatchingViewer(
4821           Entry<String, StructureViewerModel> viewerData)
4822   {
4823     final String sviewid = viewerData.getKey();
4824     final StructureViewerModel svattrib = viewerData.getValue();
4825     StructureViewerBase comp = null;
4826     JInternalFrame[] frames = getAllFrames();
4827     for (JInternalFrame frame : frames)
4828     {
4829       if (frame instanceof StructureViewerBase)
4830       {
4831         /*
4832          * Post jalview 2.4 schema includes structure view id
4833          */
4834         if (sviewid != null && ((StructureViewerBase) frame).getViewId()
4835                 .equals(sviewid))
4836         {
4837           comp = (StructureViewerBase) frame;
4838           break; // break added in 2.9
4839         }
4840         /*
4841          * Otherwise test for matching position and size of viewer frame
4842          */
4843         else if (frame.getX() == svattrib.getX()
4844                 && frame.getY() == svattrib.getY()
4845                 && frame.getHeight() == svattrib.getHeight()
4846                 && frame.getWidth() == svattrib.getWidth())
4847         {
4848           comp = (StructureViewerBase) frame;
4849           // no break in faint hope of an exact match on viewId
4850         }
4851       }
4852     }
4853     return comp;
4854   }
4855
4856   /**
4857    * Link an AlignmentPanel to an existing structure viewer.
4858    * 
4859    * @param ap
4860    * @param viewer
4861    * @param oldFiles
4862    * @param useinViewerSuperpos
4863    * @param usetoColourbyseq
4864    * @param viewerColouring
4865    */
4866   protected void linkStructureViewer(AlignmentPanel ap,
4867           StructureViewerBase viewer, StructureViewerModel stateData)
4868   {
4869     // NOTE: if the jalview project is part of a shared session then
4870     // view synchronization should/could be done here.
4871
4872     final boolean useinViewerSuperpos = stateData.isAlignWithPanel();
4873     final boolean usetoColourbyseq = stateData.isColourWithAlignPanel();
4874     final boolean viewerColouring = stateData.isColourByViewer();
4875     Map<File, StructureData> oldFiles = stateData.getFileData();
4876
4877     /*
4878      * Add mapping for sequences in this view to an already open viewer
4879      */
4880     final AAStructureBindingModel binding = viewer.getBinding();
4881     for (File id : oldFiles.keySet())
4882     {
4883       // add this and any other pdb files that should be present in the
4884       // viewer
4885       StructureData filedat = oldFiles.get(id);
4886       String pdbFile = filedat.getFilePath();
4887       SequenceI[] seq = filedat.getSeqList().toArray(new SequenceI[0]);
4888       binding.getSsm().setMapping(seq, null, pdbFile, DataSourceType.FILE,
4889               null);
4890       binding.addSequenceForStructFile(pdbFile, seq);
4891     }
4892     // and add the AlignmentPanel's reference to the view panel
4893     viewer.addAlignmentPanel(ap);
4894     if (useinViewerSuperpos)
4895     {
4896       viewer.useAlignmentPanelForSuperposition(ap);
4897     }
4898     else
4899     {
4900       viewer.excludeAlignmentPanelForSuperposition(ap);
4901     }
4902     if (usetoColourbyseq)
4903     {
4904       viewer.useAlignmentPanelForColourbyseq(ap, !viewerColouring);
4905     }
4906     else
4907     {
4908       viewer.excludeAlignmentPanelForColourbyseq(ap);
4909     }
4910   }
4911
4912   /**
4913    * Get all frames within the Desktop.
4914    * 
4915    * @return
4916    */
4917   protected JInternalFrame[] getAllFrames()
4918   {
4919     JInternalFrame[] frames = null;
4920     // TODO is this necessary - is it safe - risk of hanging?
4921     do
4922     {
4923       try
4924       {
4925         frames = Desktop.getDesktopPane().getAllFrames();
4926       } catch (ArrayIndexOutOfBoundsException e)
4927       {
4928         // occasional No such child exceptions are thrown here...
4929         try
4930         {
4931           Thread.sleep(10);
4932         } catch (InterruptedException f)
4933         {
4934         }
4935       }
4936     } while (frames == null);
4937     return frames;
4938   }
4939
4940   /**
4941    * Answers true if 'version' is equal to or later than 'supported', where each
4942    * is formatted as major/minor versions like "2.8.3" or "2.3.4b1" for bugfix
4943    * changes. Development and test values for 'version' are leniently treated
4944    * i.e. answer true.
4945    * 
4946    * @param supported
4947    *          - minimum version we are comparing against
4948    * @param version
4949    *          - version of data being processsed
4950    * @return
4951    */
4952   public static boolean isVersionStringLaterThan(String supported,
4953           String version)
4954   {
4955     if (supported == null || version == null
4956             || version.equalsIgnoreCase("DEVELOPMENT BUILD")
4957             || version.equalsIgnoreCase("Test")
4958             || version.equalsIgnoreCase("AUTOMATED BUILD"))
4959     {
4960       System.err.println("Assuming project file with "
4961               + (version == null ? "null" : version)
4962               + " is compatible with Jalview version " + supported);
4963       return true;
4964     }
4965     else
4966     {
4967       return StringUtils.compareVersions(version, supported, "b") >= 0;
4968     }
4969   }
4970
4971   Vector<JalviewStructureDisplayI> newStructureViewers = null;
4972
4973   protected void addNewStructureViewer(JalviewStructureDisplayI sview)
4974   {
4975     if (newStructureViewers != null)
4976     {
4977       sview.getBinding().setFinishedLoadingFromArchive(false);
4978       newStructureViewers.add(sview);
4979     }
4980   }
4981
4982   protected void setLoadingFinishedForNewStructureViewers()
4983   {
4984     if (newStructureViewers != null)
4985     {
4986       for (JalviewStructureDisplayI sview : newStructureViewers)
4987       {
4988         sview.getBinding().setFinishedLoadingFromArchive(true);
4989       }
4990       newStructureViewers.clear();
4991       newStructureViewers = null;
4992     }
4993   }
4994
4995   AlignFrame loadViewport(String fileName, List<JSeq> JSEQ,
4996           List<SequenceI> hiddenSeqs, AlignmentI al, JalviewModel jm,
4997           Viewport view, String uniqueSeqSetId, String viewId,
4998           List<JvAnnotRow> autoAlan)
4999   {
5000     AlignFrame af = null;
5001     af = new AlignFrame(al, safeInt(view.getWidth()),
5002             safeInt(view.getHeight()), uniqueSeqSetId, viewId)
5003     // {
5004     //
5005     // @Override
5006     // protected void processKeyEvent(java.awt.event.KeyEvent e) {
5007     // System.out.println("Jalview2XML AF " + e);
5008     // super.processKeyEvent(e);
5009     //
5010     // }
5011     //
5012     // }
5013     ;
5014     af.alignPanel.setHoldRepaint(true);
5015     af.setFileName(fileName, FileFormat.Jalview);
5016     af.setFileObject(jarFile); // BH 2019 JAL-3436
5017
5018     final AlignViewport viewport = af.getViewport();
5019     for (int i = 0; i < JSEQ.size(); i++)
5020     {
5021       int colour = safeInt(JSEQ.get(i).getColour());
5022       viewport.setSequenceColour(viewport.getAlignment().getSequenceAt(i),
5023               new Color(colour));
5024     }
5025
5026     if (al.hasSeqrep())
5027     {
5028       viewport.setColourByReferenceSeq(true);
5029       viewport.setDisplayReferenceSeq(true);
5030     }
5031
5032     viewport.setGatherViewsHere(safeBoolean(view.isGatheredViews()));
5033
5034     if (view.getSequenceSetId() != null)
5035     {
5036       AlignmentViewport av = viewportsAdded.get(uniqueSeqSetId);
5037
5038       viewport.setSequenceSetId(uniqueSeqSetId);
5039       if (av != null)
5040       {
5041         // propagate shared settings to this new view
5042         viewport.setHistoryList(av.getHistoryList());
5043         viewport.setRedoList(av.getRedoList());
5044       }
5045       else
5046       {
5047         viewportsAdded.put(uniqueSeqSetId, viewport);
5048       }
5049       // TODO: check if this method can be called repeatedly without
5050       // side-effects if alignpanel already registered.
5051       PaintRefresher.Register(af.alignPanel, uniqueSeqSetId);
5052     }
5053     // apply Hidden regions to view.
5054     if (hiddenSeqs != null)
5055     {
5056       for (int s = 0; s < JSEQ.size(); s++)
5057       {
5058         SequenceGroup hidden = new SequenceGroup();
5059         boolean isRepresentative = false;
5060         for (int r = 0; r < JSEQ.get(s).getHiddenSequences().size(); r++)
5061         {
5062           isRepresentative = true;
5063           SequenceI sequenceToHide = al
5064                   .getSequenceAt(JSEQ.get(s).getHiddenSequences().get(r));
5065           hidden.addSequence(sequenceToHide, false);
5066           // remove from hiddenSeqs list so we don't try to hide it twice
5067           hiddenSeqs.remove(sequenceToHide);
5068         }
5069         if (isRepresentative)
5070         {
5071           SequenceI representativeSequence = al.getSequenceAt(s);
5072           hidden.addSequence(representativeSequence, false);
5073           viewport.hideRepSequences(representativeSequence, hidden);
5074         }
5075       }
5076
5077       SequenceI[] hseqs = hiddenSeqs
5078               .toArray(new SequenceI[hiddenSeqs.size()]);
5079       viewport.hideSequence(hseqs);
5080
5081     }
5082     // recover view properties and display parameters
5083
5084     viewport.setShowAnnotation(safeBoolean(view.isShowAnnotation()));
5085     viewport.setAbovePIDThreshold(safeBoolean(view.isPidSelected()));
5086     final int pidThreshold = safeInt(view.getPidThreshold());
5087     viewport.setThreshold(pidThreshold);
5088
5089     viewport.setColourText(safeBoolean(view.isShowColourText()));
5090
5091     viewport.setConservationSelected(
5092             safeBoolean(view.isConservationSelected()));
5093     viewport.setIncrement(safeInt(view.getConsThreshold()));
5094     viewport.setShowJVSuffix(safeBoolean(view.isShowFullId()));
5095     viewport.setRightAlignIds(safeBoolean(view.isRightAlignIds()));
5096     viewport.setFont(new Font(view.getFontName(),
5097             safeInt(view.getFontStyle()), safeInt(view.getFontSize())),
5098             true);
5099     ViewStyleI vs = viewport.getViewStyle();
5100     vs.setScaleProteinAsCdna(view.isScaleProteinAsCdna());
5101     viewport.setViewStyle(vs);
5102     // TODO: allow custom charWidth/Heights to be restored by updating them
5103     // after setting font - which means set above to false
5104     viewport.setRenderGaps(safeBoolean(view.isRenderGaps()));
5105     viewport.setWrapAlignment(safeBoolean(view.isWrapAlignment()));
5106     viewport.setShowAnnotation(safeBoolean(view.isShowAnnotation()));
5107
5108     viewport.setShowBoxes(safeBoolean(view.isShowBoxes()));
5109
5110     viewport.setShowText(safeBoolean(view.isShowText()));
5111
5112     viewport.setTextColour(new Color(safeInt(view.getTextCol1())));
5113     viewport.setTextColour2(new Color(safeInt(view.getTextCol2())));
5114     viewport.setThresholdTextColour(safeInt(view.getTextColThreshold()));
5115     viewport.setShowUnconserved(view.isShowUnconserved());
5116     viewport.getRanges().setStartRes(safeInt(view.getStartRes()));
5117
5118     if (view.getViewName() != null)
5119     {
5120       viewport.setViewName(view.getViewName());
5121       af.setInitialTabVisible();
5122     }
5123     int x = safeInt(view.getXpos());
5124     int y = safeInt(view.getYpos());
5125     int w = safeInt(view.getWidth());
5126     int h = safeInt(view.getHeight());
5127     // // BH we cannot let the title bar go off the top
5128     // if (Platform.isJS())
5129     // {
5130     // x = Math.max(50 - w, x);
5131     // y = Math.max(0, y);
5132     // }
5133
5134     af.setBounds(x, y, w, h);
5135     // startSeq set in af.alignPanel.updateLayout below
5136     af.alignPanel.updateLayout();
5137     ColourSchemeI cs = null;
5138     // apply colourschemes
5139     if (view.getBgColour() != null)
5140     {
5141       if (view.getBgColour().startsWith("ucs"))
5142       {
5143         cs = getUserColourScheme(jm, view.getBgColour());
5144       }
5145       else if (view.getBgColour().startsWith("Annotation"))
5146       {
5147         AnnotationColourScheme viewAnnColour = view.getAnnotationColours();
5148         cs = constructAnnotationColour(viewAnnColour, af, al, jm, true);
5149
5150         // annpos
5151
5152       }
5153       else
5154       {
5155         cs = ColourSchemeProperty.getColourScheme(af.getViewport(), al,
5156                 view.getBgColour());
5157       }
5158     }
5159
5160     /*
5161      * turn off 'alignment colour applies to all groups'
5162      * while restoring global colour scheme
5163      */
5164     viewport.setColourAppliesToAllGroups(false);
5165     viewport.setGlobalColourScheme(cs);
5166     viewport.getResidueShading().setThreshold(pidThreshold,
5167             view.isIgnoreGapsinConsensus());
5168     viewport.getResidueShading()
5169             .setConsensus(viewport.getSequenceConsensusHash());
5170     if (safeBoolean(view.isConservationSelected()) && cs != null)
5171     {
5172       viewport.getResidueShading()
5173               .setConservationInc(safeInt(view.getConsThreshold()));
5174     }
5175     af.changeColour(cs);
5176     viewport.setColourAppliesToAllGroups(true);
5177
5178     viewport.setShowSequenceFeatures(
5179             safeBoolean(view.isShowSequenceFeatures()));
5180
5181     viewport.setCentreColumnLabels(view.isCentreColumnLabels());
5182     viewport.setIgnoreGapsConsensus(view.isIgnoreGapsinConsensus(), null);
5183     viewport.setFollowHighlight(view.isFollowHighlight());
5184     viewport.followSelection = view.isFollowSelection();
5185     viewport.setShowConsensusHistogram(view.isShowConsensusHistogram());
5186     viewport.setShowSequenceLogo(view.isShowSequenceLogo());
5187     viewport.setNormaliseSequenceLogo(view.isNormaliseSequenceLogo());
5188     viewport.setShowDBRefs(safeBoolean(view.isShowDbRefTooltip()));
5189     viewport.setShowNPFeats(safeBoolean(view.isShowNPfeatureTooltip()));
5190     viewport.setShowGroupConsensus(view.isShowGroupConsensus());
5191     viewport.setShowGroupConservation(view.isShowGroupConservation());
5192     viewport.setShowComplementFeatures(view.isShowComplementFeatures());
5193     viewport.setShowComplementFeaturesOnTop(
5194             view.isShowComplementFeaturesOnTop());
5195
5196     // recover feature settings
5197     if (jm.getFeatureSettings() != null)
5198     {
5199       FeatureRenderer fr = af.alignPanel.getSeqPanel().seqCanvas
5200               .getFeatureRenderer();
5201       FeaturesDisplayed fdi;
5202       viewport.setFeaturesDisplayed(fdi = new FeaturesDisplayed());
5203       String[] renderOrder = new String[jm.getFeatureSettings().getSetting()
5204               .size()];
5205       Map<String, FeatureColourI> featureColours = new Hashtable<>();
5206       Map<String, Float> featureOrder = new Hashtable<>();
5207
5208       for (int fs = 0; fs < jm.getFeatureSettings().getSetting()
5209               .size(); fs++)
5210       {
5211         Setting setting = jm.getFeatureSettings().getSetting().get(fs);
5212         String featureType = setting.getType();
5213
5214         /*
5215          * restore feature filters (if any)
5216          */
5217         jalview.xml.binding.jalview.FeatureMatcherSet filters = setting
5218                 .getMatcherSet();
5219         if (filters != null)
5220         {
5221           FeatureMatcherSetI filter = Jalview2XML.parseFilter(featureType,
5222                   filters);
5223           if (!filter.isEmpty())
5224           {
5225             fr.setFeatureFilter(featureType, filter);
5226           }
5227         }
5228
5229         /*
5230          * restore feature colour scheme
5231          */
5232         Color maxColour = new Color(setting.getColour());
5233         if (setting.getMincolour() != null)
5234         {
5235           /*
5236            * minColour is always set unless a simple colour
5237            * (including for colour by label though it doesn't use it)
5238            */
5239           Color minColour = new Color(setting.getMincolour().intValue());
5240           Color noValueColour = minColour;
5241           NoValueColour noColour = setting.getNoValueColour();
5242           if (noColour == NoValueColour.NONE)
5243           {
5244             noValueColour = null;
5245           }
5246           else if (noColour == NoValueColour.MAX)
5247           {
5248             noValueColour = maxColour;
5249           }
5250           float min = safeFloat(safeFloat(setting.getMin()));
5251           float max = setting.getMax() == null ? 1f
5252                   : setting.getMax().floatValue();
5253           FeatureColourI gc = new FeatureColour(maxColour, minColour,
5254                   maxColour, noValueColour, min, max);
5255           if (setting.getAttributeName().size() > 0)
5256           {
5257             gc.setAttributeName(setting.getAttributeName().toArray(
5258                     new String[setting.getAttributeName().size()]));
5259           }
5260           if (setting.getThreshold() != null)
5261           {
5262             gc.setThreshold(setting.getThreshold().floatValue());
5263             int threshstate = safeInt(setting.getThreshstate());
5264             // -1 = None, 0 = Below, 1 = Above threshold
5265             if (threshstate == 0)
5266             {
5267               gc.setBelowThreshold(true);
5268             }
5269             else if (threshstate == 1)
5270             {
5271               gc.setAboveThreshold(true);
5272             }
5273           }
5274           gc.setAutoScaled(true); // default
5275           if (setting.isAutoScale() != null)
5276           {
5277             gc.setAutoScaled(setting.isAutoScale());
5278           }
5279           if (setting.isColourByLabel() != null)
5280           {
5281             gc.setColourByLabel(setting.isColourByLabel());
5282           }
5283           // and put in the feature colour table.
5284           featureColours.put(featureType, gc);
5285         }
5286         else
5287         {
5288           featureColours.put(featureType, new FeatureColour(maxColour));
5289         }
5290         renderOrder[fs] = featureType;
5291         if (setting.getOrder() != null)
5292         {
5293           featureOrder.put(featureType, setting.getOrder().floatValue());
5294         }
5295         else
5296         {
5297           featureOrder.put(featureType, Float.valueOf(
5298                   fs / jm.getFeatureSettings().getSetting().size()));
5299         }
5300         if (safeBoolean(setting.isDisplay()))
5301         {
5302           fdi.setVisible(featureType);
5303         }
5304       }
5305       Map<String, Boolean> fgtable = new Hashtable<>();
5306       for (int gs = 0; gs < jm.getFeatureSettings().getGroup().size(); gs++)
5307       {
5308         Group grp = jm.getFeatureSettings().getGroup().get(gs);
5309         fgtable.put(grp.getName(), Boolean.valueOf(grp.isDisplay()));
5310       }
5311       // FeatureRendererSettings frs = new FeatureRendererSettings(renderOrder,
5312       // fgtable, featureColours, jms.getFeatureSettings().hasTransparency() ?
5313       // jms.getFeatureSettings().getTransparency() : 0.0, featureOrder);
5314       FeatureRendererSettings frs = new FeatureRendererSettings(renderOrder,
5315               fgtable, featureColours, 1.0f, featureOrder);
5316       fr.transferSettings(frs);
5317     }
5318
5319     if (view.getHiddenColumns().size() > 0)
5320     {
5321       for (int c = 0; c < view.getHiddenColumns().size(); c++)
5322       {
5323         final HiddenColumns hc = view.getHiddenColumns().get(c);
5324         viewport.hideColumns(safeInt(hc.getStart()),
5325                 safeInt(hc.getEnd()) /* +1 */);
5326       }
5327     }
5328     if (view.getCalcIdParam() != null)
5329     {
5330       for (CalcIdParam calcIdParam : view.getCalcIdParam())
5331       {
5332         if (calcIdParam != null)
5333         {
5334           if (recoverCalcIdParam(calcIdParam, viewport))
5335           {
5336           }
5337           else
5338           {
5339             warn("Couldn't recover parameters for "
5340                     + calcIdParam.getCalcId());
5341           }
5342         }
5343       }
5344     }
5345     af.setMenusFromViewport(viewport);
5346     af.setTitle(view.getTitle());
5347     // TODO: we don't need to do this if the viewport is aready visible.
5348     /*
5349      * Add the AlignFrame to the desktop (it may be 'gathered' later), unless it
5350      * has a 'cdna/protein complement' view, in which case save it in order to
5351      * populate a SplitFrame once all views have been read in.
5352      */
5353     String complementaryViewId = view.getComplementId();
5354     if (complementaryViewId == null)
5355     {
5356       Dimension dim = Platform.getDimIfEmbedded(af,
5357               safeInt(view.getWidth()), safeInt(view.getHeight()));
5358       Desktop.addInternalFrame(af, view.getTitle(), dim.width, dim.height);
5359       // recompute any autoannotation
5360       af.alignPanel.updateAnnotation(false, true);
5361       reorderAutoannotation(af, al, autoAlan);
5362       af.alignPanel.alignmentChanged();
5363     }
5364     else
5365     {
5366       splitFrameCandidates.put(view, af);
5367     }
5368     return af;
5369   }
5370
5371   /**
5372    * Reads saved data to restore Colour by Annotation settings
5373    * 
5374    * @param viewAnnColour
5375    * @param af
5376    * @param al
5377    * @param model
5378    * @param checkGroupAnnColour
5379    * @return
5380    */
5381   private ColourSchemeI constructAnnotationColour(
5382           AnnotationColourScheme viewAnnColour, AlignFrame af,
5383           AlignmentI al, JalviewModel model, boolean checkGroupAnnColour)
5384   {
5385     boolean propagateAnnColour = false;
5386     AlignmentI annAlignment = af != null ? af.getViewport().getAlignment()
5387             : al;
5388     if (checkGroupAnnColour && al.getGroups() != null
5389             && al.getGroups().size() > 0)
5390     {
5391       // pre 2.8.1 behaviour
5392       // check to see if we should transfer annotation colours
5393       propagateAnnColour = true;
5394       for (SequenceGroup sg : al.getGroups())
5395       {
5396         if (sg.getColourScheme() instanceof AnnotationColourGradient)
5397         {
5398           propagateAnnColour = false;
5399         }
5400       }
5401     }
5402
5403     /*
5404      * 2.10.2- : saved annotationId is AlignmentAnnotation.annotationId
5405      */
5406     String annotationId = viewAnnColour.getAnnotation();
5407     AlignmentAnnotation matchedAnnotation = annotationIds.get(annotationId);
5408
5409     /*
5410      * pre 2.10.2: saved annotationId is AlignmentAnnotation.label
5411      */
5412     if (matchedAnnotation == null
5413             && annAlignment.getAlignmentAnnotation() != null)
5414     {
5415       for (int i = 0; i < annAlignment.getAlignmentAnnotation().length; i++)
5416       {
5417         if (annotationId
5418                 .equals(annAlignment.getAlignmentAnnotation()[i].label))
5419         {
5420           matchedAnnotation = annAlignment.getAlignmentAnnotation()[i];
5421           break;
5422         }
5423       }
5424     }
5425     if (matchedAnnotation == null)
5426     {
5427       System.err.println("Failed to match annotation colour scheme for "
5428               + annotationId);
5429       return null;
5430     }
5431     if (matchedAnnotation.getThreshold() == null)
5432     {
5433       matchedAnnotation.setThreshold(
5434               new GraphLine(safeFloat(viewAnnColour.getThreshold()),
5435                       "Threshold", Color.black));
5436     }
5437
5438     AnnotationColourGradient cs = null;
5439     if (viewAnnColour.getColourScheme().equals("None"))
5440     {
5441       cs = new AnnotationColourGradient(matchedAnnotation,
5442               new Color(safeInt(viewAnnColour.getMinColour())),
5443               new Color(safeInt(viewAnnColour.getMaxColour())),
5444               safeInt(viewAnnColour.getAboveThreshold()));
5445     }
5446     else if (viewAnnColour.getColourScheme().startsWith("ucs"))
5447     {
5448       cs = new AnnotationColourGradient(matchedAnnotation,
5449               getUserColourScheme(model, viewAnnColour.getColourScheme()),
5450               safeInt(viewAnnColour.getAboveThreshold()));
5451     }
5452     else
5453     {
5454       cs = new AnnotationColourGradient(matchedAnnotation,
5455               ColourSchemeProperty.getColourScheme(af.getViewport(), al,
5456                       viewAnnColour.getColourScheme()),
5457               safeInt(viewAnnColour.getAboveThreshold()));
5458     }
5459
5460     boolean perSequenceOnly = safeBoolean(viewAnnColour.isPerSequence());
5461     boolean useOriginalColours = safeBoolean(
5462             viewAnnColour.isPredefinedColours());
5463     cs.setSeqAssociated(perSequenceOnly);
5464     cs.setPredefinedColours(useOriginalColours);
5465
5466     if (propagateAnnColour && al.getGroups() != null)
5467     {
5468       // Also use these settings for all the groups
5469       for (int g = 0; g < al.getGroups().size(); g++)
5470       {
5471         SequenceGroup sg = al.getGroups().get(g);
5472         if (sg.getGroupColourScheme() == null)
5473         {
5474           continue;
5475         }
5476
5477         AnnotationColourGradient groupScheme = new AnnotationColourGradient(
5478                 matchedAnnotation, sg.getColourScheme(),
5479                 safeInt(viewAnnColour.getAboveThreshold()));
5480         sg.setColourScheme(groupScheme);
5481         groupScheme.setSeqAssociated(perSequenceOnly);
5482         groupScheme.setPredefinedColours(useOriginalColours);
5483       }
5484     }
5485     return cs;
5486   }
5487
5488   private void reorderAutoannotation(AlignFrame af, AlignmentI al,
5489           List<JvAnnotRow> autoAlan)
5490   {
5491     // copy over visualization settings for autocalculated annotation in the
5492     // view
5493     if (al.getAlignmentAnnotation() != null)
5494     {
5495       /**
5496        * Kludge for magic autoannotation names (see JAL-811)
5497        */
5498       String[] magicNames = new String[] { "Consensus", "Quality",
5499           "Conservation" };
5500       JvAnnotRow nullAnnot = new JvAnnotRow(-1, null);
5501       Hashtable<String, JvAnnotRow> visan = new Hashtable<>();
5502       for (String nm : magicNames)
5503       {
5504         visan.put(nm, nullAnnot);
5505       }
5506       for (JvAnnotRow auan : autoAlan)
5507       {
5508         visan.put(auan.template.label
5509                 + (auan.template.getCalcId() == null ? ""
5510                         : "\t" + auan.template.getCalcId()),
5511                 auan);
5512       }
5513       int hSize = al.getAlignmentAnnotation().length;
5514       List<JvAnnotRow> reorder = new ArrayList<>();
5515       // work through any autoCalculated annotation already on the view
5516       // removing it if it should be placed in a different location on the
5517       // annotation panel.
5518       List<String> remains = new ArrayList<>(visan.keySet());
5519       for (int h = 0; h < hSize; h++)
5520       {
5521         jalview.datamodel.AlignmentAnnotation jalan = al
5522                 .getAlignmentAnnotation()[h];
5523         if (jalan.autoCalculated)
5524         {
5525           String k;
5526           JvAnnotRow valan = visan.get(k = jalan.label);
5527           if (jalan.getCalcId() != null)
5528           {
5529             valan = visan.get(k = jalan.label + "\t" + jalan.getCalcId());
5530           }
5531
5532           if (valan != null)
5533           {
5534             // delete the auto calculated row from the alignment
5535             al.deleteAnnotation(jalan, false);
5536             remains.remove(k);
5537             hSize--;
5538             h--;
5539             if (valan != nullAnnot)
5540             {
5541               if (jalan != valan.template)
5542               {
5543                 // newly created autoannotation row instance
5544                 // so keep a reference to the visible annotation row
5545                 // and copy over all relevant attributes
5546                 if (valan.template.graphHeight >= 0)
5547
5548                 {
5549                   jalan.graphHeight = valan.template.graphHeight;
5550                 }
5551                 jalan.visible = valan.template.visible;
5552               }
5553               reorder.add(new JvAnnotRow(valan.order, jalan));
5554             }
5555           }
5556         }
5557       }
5558       // Add any (possibly stale) autocalculated rows that were not appended to
5559       // the view during construction
5560       for (String other : remains)
5561       {
5562         JvAnnotRow othera = visan.get(other);
5563         if (othera != nullAnnot && othera.template.getCalcId() != null
5564                 && othera.template.getCalcId().length() > 0)
5565         {
5566           reorder.add(othera);
5567         }
5568       }
5569       // now put the automatic annotation in its correct place
5570       int s = 0, srt[] = new int[reorder.size()];
5571       JvAnnotRow[] rws = new JvAnnotRow[reorder.size()];
5572       for (JvAnnotRow jvar : reorder)
5573       {
5574         rws[s] = jvar;
5575         srt[s++] = jvar.order;
5576       }
5577       reorder.clear();
5578       jalview.util.QuickSort.sort(srt, rws);
5579       // and re-insert the annotation at its correct position
5580       for (JvAnnotRow jvar : rws)
5581       {
5582         al.addAnnotation(jvar.template, jvar.order);
5583       }
5584       af.alignPanel.adjustAnnotationHeight();
5585     }
5586   }
5587
5588   Hashtable skipList = null;
5589
5590   /**
5591    * TODO remove this method
5592    * 
5593    * @param view
5594    * @return AlignFrame bound to sequenceSetId from view, if one exists. private
5595    *         AlignFrame getSkippedFrame(Viewport view) { if (skipList==null) {
5596    *         throw new Error("Implementation Error. No skipList defined for this
5597    *         Jalview2XML instance."); } return (AlignFrame)
5598    *         skipList.get(view.getSequenceSetId()); }
5599    */
5600
5601   /**
5602    * Check if the Jalview view contained in object should be skipped or not.
5603    * 
5604    * @param object
5605    * @return true if view's sequenceSetId is a key in skipList
5606    */
5607   private boolean skipViewport(JalviewModel object)
5608   {
5609     if (skipList == null)
5610     {
5611       return false;
5612     }
5613     String id = object.getViewport().get(0).getSequenceSetId();
5614     if (skipList.containsKey(id))
5615     {
5616       if (Cache.log != null && Cache.log.isDebugEnabled())
5617       {
5618         Cache.log.debug("Skipping seuqence set id " + id);
5619       }
5620       return true;
5621     }
5622     return false;
5623   }
5624
5625   protected void addToSkipList(AlignFrame af)
5626   {
5627     if (skipList == null)
5628     {
5629       skipList = new Hashtable();
5630     }
5631     skipList.put(af.getViewport().getSequenceSetId(), af);
5632   }
5633
5634   protected void clearSkipList()
5635   {
5636     if (skipList != null)
5637     {
5638       skipList.clear();
5639       skipList = null;
5640     }
5641   }
5642
5643   private void recoverDatasetFor(SequenceSet vamsasSet, AlignmentI al,
5644           boolean ignoreUnrefed, String uniqueSeqSetId)
5645   {
5646     jalview.datamodel.AlignmentI ds = getDatasetFor(
5647             vamsasSet.getDatasetId());
5648     AlignmentI xtant_ds = ds;
5649     if (xtant_ds == null)
5650     {
5651       // good chance we are about to create a new dataset, but check if we've
5652       // seen some of the dataset sequence IDs before.
5653       // TODO: skip this check if we are working with project generated by
5654       // version 2.11 or later
5655       xtant_ds = checkIfHasDataset(vamsasSet.getSequence());
5656       if (xtant_ds != null)
5657       {
5658         ds = xtant_ds;
5659         addDatasetRef(vamsasSet.getDatasetId(), ds);
5660       }
5661     }
5662     Vector dseqs = null;
5663     if (!ignoreUnrefed)
5664     {
5665       // recovering an alignment View
5666       AlignmentI seqSetDS = getDatasetFor(UNIQSEQSETID + uniqueSeqSetId);
5667       if (seqSetDS != null)
5668       {
5669         if (ds != null && ds != seqSetDS)
5670         {
5671           warn("JAL-3171 regression: Overwriting a dataset reference for an alignment"
5672                   + " - CDS/Protein crossreference data may be lost");
5673           if (xtant_ds != null)
5674           {
5675             // This can only happen if the unique sequence set ID was bound to a
5676             // dataset that did not contain any of the sequences in the view
5677             // currently being restored.
5678             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.");
5679           }
5680         }
5681         ds = seqSetDS;
5682         addDatasetRef(vamsasSet.getDatasetId(), ds);
5683       }
5684     }
5685     if (ds == null)
5686     {
5687       // try even harder to restore dataset
5688       AlignmentI xtantDS = checkIfHasDataset(vamsasSet.getSequence());
5689       // create a list of new dataset sequences
5690       dseqs = new Vector();
5691     }
5692     for (int i = 0, iSize = vamsasSet.getSequence().size(); i < iSize; i++)
5693     {
5694       Sequence vamsasSeq = vamsasSet.getSequence().get(i);
5695       ensureJalviewDatasetSequence(vamsasSeq, ds, dseqs, ignoreUnrefed, i);
5696     }
5697     // create a new dataset
5698     if (ds == null)
5699     {
5700       SequenceI[] dsseqs = new SequenceI[dseqs.size()];
5701       dseqs.copyInto(dsseqs);
5702       ds = new jalview.datamodel.Alignment(dsseqs);
5703 //      debug("Jalview2XML Created new dataset " + vamsasSet.getDatasetId()
5704 //              + " for alignment " + System.identityHashCode(al));
5705       addDatasetRef(vamsasSet.getDatasetId(), ds);
5706     }
5707     // set the dataset for the newly imported alignment.
5708     if (al.getDataset() == null && !ignoreUnrefed)
5709     {
5710       al.setDataset(ds);
5711       // register dataset for the alignment's uniqueSeqSetId for legacy projects
5712       addDatasetRef(UNIQSEQSETID + uniqueSeqSetId, ds);
5713     }
5714     updateSeqDatasetBinding(vamsasSet.getSequence(), ds);
5715   }
5716
5717   /**
5718    * XML dataset sequence ID to materialised dataset reference
5719    */
5720   HashMap<String, AlignmentI> seqToDataset = new HashMap<>();
5721
5722   /**
5723    * @return the first materialised dataset reference containing a dataset
5724    *         sequence referenced in the given view
5725    * @param list
5726    *          - sequences from the view
5727    */
5728   AlignmentI checkIfHasDataset(List<Sequence> list)
5729   {
5730     for (Sequence restoredSeq : list)
5731     {
5732       AlignmentI datasetFor = seqToDataset.get(restoredSeq.getDsseqid());
5733       if (datasetFor != null)
5734       {
5735         return datasetFor;
5736       }
5737     }
5738     return null;
5739   }
5740
5741   /**
5742    * Register ds as the containing dataset for the dataset sequences referenced
5743    * by sequences in list
5744    * 
5745    * @param list
5746    *          - sequences in a view
5747    * @param ds
5748    */
5749   void updateSeqDatasetBinding(List<Sequence> list, AlignmentI ds)
5750   {
5751     for (Sequence restoredSeq : list)
5752     {
5753       AlignmentI prevDS = seqToDataset.put(restoredSeq.getDsseqid(), ds);
5754       if (prevDS != null && prevDS != ds)
5755       {
5756         warn("Dataset sequence appears in many datasets: "
5757                 + restoredSeq.getDsseqid());
5758         // TODO: try to merge!
5759       }
5760     }
5761   }
5762   /**
5763    * 
5764    * @param vamsasSeq
5765    *          sequence definition to create/merge dataset sequence for
5766    * @param ds
5767    *          dataset alignment
5768    * @param dseqs
5769    *          vector to add new dataset sequence to
5770    * @param ignoreUnrefed
5771    *          - when true, don't create new sequences from vamsasSeq if it's id
5772    *          doesn't already have an asssociated Jalview sequence.
5773    * @param vseqpos
5774    *          - used to reorder the sequence in the alignment according to the
5775    *          vamsasSeq array ordering, to preserve ordering of dataset
5776    */
5777   private void ensureJalviewDatasetSequence(Sequence vamsasSeq,
5778           AlignmentI ds, Vector dseqs, boolean ignoreUnrefed, int vseqpos)
5779   {
5780     // JBP TODO: Check this is called for AlCodonFrames to support recovery of
5781     // xRef Codon Maps
5782     SequenceI sq = seqRefIds.get(vamsasSeq.getId());
5783     boolean reorder = false;
5784     SequenceI dsq = null;
5785     if (sq != null && sq.getDatasetSequence() != null)
5786     {
5787       dsq = sq.getDatasetSequence();
5788     }
5789     else
5790     {
5791       reorder = true;
5792     }
5793     if (sq == null && ignoreUnrefed)
5794     {
5795       return;
5796     }
5797     String sqid = vamsasSeq.getDsseqid();
5798     if (dsq == null)
5799     {
5800       // need to create or add a new dataset sequence reference to this sequence
5801       if (sqid != null)
5802       {
5803         dsq = seqRefIds.get(sqid);
5804       }
5805       // check again
5806       if (dsq == null)
5807       {
5808         // make a new dataset sequence
5809         dsq = sq.createDatasetSequence();
5810         if (sqid == null)
5811         {
5812           // make up a new dataset reference for this sequence
5813           sqid = seqHash(dsq);
5814         }
5815         dsq.setVamsasId(uniqueSetSuffix + sqid);
5816         seqRefIds.put(sqid, dsq);
5817         if (ds == null)
5818         {
5819           if (dseqs != null)
5820           {
5821             dseqs.addElement(dsq);
5822           }
5823         }
5824         else
5825         {
5826           ds.addSequence(dsq);
5827         }
5828       }
5829       else
5830       {
5831         if (sq != dsq)
5832         { // make this dataset sequence sq's dataset sequence
5833           sq.setDatasetSequence(dsq);
5834           // and update the current dataset alignment
5835           if (ds == null)
5836           {
5837             if (dseqs != null)
5838             {
5839               if (!dseqs.contains(dsq))
5840               {
5841                 dseqs.add(dsq);
5842               }
5843             }
5844             else
5845             {
5846               if (ds.findIndex(dsq) < 0)
5847               {
5848                 ds.addSequence(dsq);
5849               }
5850             }
5851           }
5852         }
5853       }
5854     }
5855     // TODO: refactor this as a merge dataset sequence function
5856     // now check that sq (the dataset sequence) sequence really is the union of
5857     // all references to it
5858     // boolean pre = sq.getStart() < dsq.getStart();
5859     // boolean post = sq.getEnd() > dsq.getEnd();
5860     // if (pre || post)
5861     if (sq != dsq)
5862     {
5863       // StringBuffer sb = new StringBuffer();
5864       String newres = jalview.analysis.AlignSeq.extractGaps(
5865               jalview.util.Comparison.GapChars, sq.getSequenceAsString());
5866       if (!newres.equalsIgnoreCase(dsq.getSequenceAsString())
5867               && newres.length() > dsq.getLength())
5868       {
5869         // Update with the longer sequence.
5870         synchronized (dsq)
5871         {
5872           /*
5873            * if (pre) { sb.insert(0, newres .substring(0, dsq.getStart() -
5874            * sq.getStart())); dsq.setStart(sq.getStart()); } if (post) {
5875            * sb.append(newres.substring(newres.length() - sq.getEnd() -
5876            * dsq.getEnd())); dsq.setEnd(sq.getEnd()); }
5877            */
5878           dsq.setSequence(newres);
5879         }
5880         // TODO: merges will never happen if we 'know' we have the real dataset
5881         // sequence - this should be detected when id==dssid
5882         System.err.println(
5883                 "DEBUG Notice:  Merged dataset sequence (if you see this often, post at http://issues.jalview.org/browse/JAL-1474)"); // ("
5884         // + (pre ? "prepended" : "") + " "
5885         // + (post ? "appended" : ""));
5886       }
5887     }
5888     else
5889     {
5890       // sequence refs are identical. We may need to update the existing dataset
5891       // alignment with this one, though.
5892       if (ds != null && dseqs == null)
5893       {
5894         int opos = ds.findIndex(dsq);
5895         SequenceI tseq = null;
5896         if (opos != -1 && vseqpos != opos)
5897         {
5898           // remove from old position
5899           ds.deleteSequence(dsq);
5900         }
5901         if (vseqpos < ds.getHeight())
5902         {
5903           if (vseqpos != opos)
5904           {
5905             // save sequence at destination position
5906             tseq = ds.getSequenceAt(vseqpos);
5907             ds.replaceSequenceAt(vseqpos, dsq);
5908             ds.addSequence(tseq);
5909           }
5910         }
5911         else
5912         {
5913           ds.addSequence(dsq);
5914         }
5915       }
5916     }
5917   }
5918
5919   /*
5920    * TODO use AlignmentI here and in related methods - needs
5921    * AlignmentI.getDataset() changed to return AlignmentI instead of Alignment
5922    */
5923   Hashtable<String, AlignmentI> datasetIds = null;
5924
5925   IdentityHashMap<AlignmentI, String> dataset2Ids = null;
5926
5927   private AlignmentI getDatasetFor(String datasetId)
5928   {
5929     if (datasetIds == null)
5930     {
5931       datasetIds = new Hashtable<>();
5932       return null;
5933     }
5934     if (datasetIds.containsKey(datasetId))
5935     {
5936       return datasetIds.get(datasetId);
5937     }
5938     return null;
5939   }
5940
5941   private void addDatasetRef(String datasetId, AlignmentI dataset)
5942   {
5943     if (datasetIds == null)
5944     {
5945       datasetIds = new Hashtable<>();
5946     }
5947     datasetIds.put(datasetId, dataset);
5948   }
5949
5950   /**
5951    * make a new dataset ID for this jalview dataset alignment
5952    * 
5953    * @param dataset
5954    * @return
5955    */
5956   private String getDatasetIdRef(AlignmentI dataset)
5957   {
5958     if (dataset.getDataset() != null)
5959     {
5960       warn("Serious issue!  Dataset Object passed to getDatasetIdRef is not a Jalview DATASET alignment...");
5961     }
5962     String datasetId = makeHashCode(dataset, null);
5963     if (datasetId == null)
5964     {
5965       // make a new datasetId and record it
5966       if (dataset2Ids == null)
5967       {
5968         dataset2Ids = new IdentityHashMap<>();
5969       }
5970       else
5971       {
5972         datasetId = dataset2Ids.get(dataset);
5973       }
5974       if (datasetId == null)
5975       {
5976         datasetId = "ds" + dataset2Ids.size() + 1;
5977         dataset2Ids.put(dataset, datasetId);
5978       }
5979     }
5980     return datasetId;
5981   }
5982
5983   /**
5984    * Add any saved DBRefEntry's to the sequence. An entry flagged as 'locus' is
5985    * constructed as a special subclass GeneLocus.
5986    * 
5987    * @param datasetSequence
5988    * @param sequence
5989    */
5990   private void addDBRefs(SequenceI datasetSequence, Sequence sequence)
5991   {
5992     for (int d = 0; d < sequence.getDBRef().size(); d++)
5993     {
5994       DBRef dr = sequence.getDBRef().get(d);
5995       DBRefEntry entry;
5996       if (dr.isLocus())
5997       {
5998         entry = new GeneLocus(dr.getSource(), dr.getVersion(),
5999                 dr.getAccessionId());
6000       }
6001       else
6002       {
6003         entry = new DBRefEntry(dr.getSource(), dr.getVersion(),
6004                 dr.getAccessionId());
6005       }
6006       if (dr.getMapping() != null)
6007       {
6008         entry.setMap(addMapping(dr.getMapping()));
6009       }
6010       datasetSequence.addDBRef(entry);
6011     }
6012   }
6013
6014   private jalview.datamodel.Mapping addMapping(Mapping m)
6015   {
6016     SequenceI dsto = null;
6017     // Mapping m = dr.getMapping();
6018     int fr[] = new int[m.getMapListFrom().size() * 2];
6019     Iterator<MapListFrom> from = m.getMapListFrom().iterator();// enumerateMapListFrom();
6020     for (int _i = 0; from.hasNext(); _i += 2)
6021     {
6022       MapListFrom mf = from.next();
6023       fr[_i] = mf.getStart();
6024       fr[_i + 1] = mf.getEnd();
6025     }
6026     int fto[] = new int[m.getMapListTo().size() * 2];
6027     Iterator<MapListTo> to = m.getMapListTo().iterator();// enumerateMapListTo();
6028     for (int _i = 0; to.hasNext(); _i += 2)
6029     {
6030       MapListTo mf = to.next();
6031       fto[_i] = mf.getStart();
6032       fto[_i + 1] = mf.getEnd();
6033     }
6034     jalview.datamodel.Mapping jmap = new jalview.datamodel.Mapping(dsto, fr,
6035             fto, m.getMapFromUnit().intValue(),
6036             m.getMapToUnit().intValue());
6037
6038     /*
6039      * (optional) choice of dseqFor or Sequence
6040      */
6041     if (m.getDseqFor() != null)
6042     {
6043       String dsfor = m.getDseqFor();
6044       if (seqRefIds.containsKey(dsfor))
6045       {
6046         /*
6047          * recover from hash
6048          */
6049         jmap.setTo(seqRefIds.get(dsfor));
6050       }
6051       else
6052       {
6053         frefedSequence.add(newMappingRef(dsfor, jmap));
6054       }
6055     }
6056     else if (m.getSequence() != null)
6057     {
6058       /*
6059        * local sequence definition
6060        */
6061       Sequence ms = m.getSequence();
6062       SequenceI djs = null;
6063       String sqid = ms.getDsseqid();
6064       if (sqid != null && sqid.length() > 0)
6065       {
6066         /*
6067          * recover dataset sequence
6068          */
6069         djs = seqRefIds.get(sqid);
6070       }
6071       else
6072       {
6073         System.err.println(
6074                 "Warning - making up dataset sequence id for DbRef sequence map reference");
6075         sqid = ((Object) ms).toString(); // make up a new hascode for
6076         // undefined dataset sequence hash
6077         // (unlikely to happen)
6078       }
6079
6080       if (djs == null)
6081       {
6082         /**
6083          * make a new dataset sequence and add it to refIds hash
6084          */
6085         djs = new jalview.datamodel.Sequence(ms.getName(),
6086                 ms.getSequence());
6087         djs.setStart(jmap.getMap().getToLowest());
6088         djs.setEnd(jmap.getMap().getToHighest());
6089         djs.setVamsasId(uniqueSetSuffix + sqid);
6090         jmap.setTo(djs);
6091         incompleteSeqs.put(sqid, djs);
6092         seqRefIds.put(sqid, djs);
6093
6094       }
6095       jalview.bin.Cache.log.debug("about to recurse on addDBRefs.");
6096       addDBRefs(djs, ms);
6097
6098     }
6099
6100     return jmap;
6101   }
6102
6103   /**
6104    * Provides a 'copy' of an alignment view (on action New View) by 'saving' the
6105    * view as XML (but not to file), and then reloading it
6106    * 
6107    * @param ap
6108    * @return
6109    */
6110   public AlignmentPanel copyAlignPanel(AlignmentPanel ap)
6111   {
6112     initSeqRefs();
6113     JalviewModel jm = saveState(ap, null, null, null);
6114
6115     addDatasetRef(
6116             jm.getVamsasModel().getSequenceSet().get(0).getDatasetId(),
6117             ap.getAlignment().getDataset());
6118
6119     uniqueSetSuffix = "";
6120     // jm.getJalviewModelSequence().getViewport(0).setId(null);
6121     jm.getViewport().get(0).setId(null);
6122     // we don't overwrite the view we just copied
6123
6124     if (this.frefedSequence == null)
6125     {
6126       frefedSequence = new Vector<>();
6127     }
6128
6129     viewportsAdded.clear();
6130
6131     AlignFrame af = loadFromObject(jm, null, false, null);
6132     af.getAlignPanels().clear();
6133     af.closeMenuItem_actionPerformed(true);
6134     af.alignPanel.setHoldRepaint(false);
6135     this.jarFile = null;
6136
6137     /*
6138      * if(ap.av.getAlignment().getAlignmentAnnotation()!=null) { for(int i=0;
6139      * i<ap.av.getAlignment().getAlignmentAnnotation().length; i++) {
6140      * if(!ap.av.getAlignment().getAlignmentAnnotation()[i].autoCalculated) {
6141      * af.alignPanel.av.getAlignment().getAlignmentAnnotation()[i] =
6142      * ap.av.getAlignment().getAlignmentAnnotation()[i]; } } }
6143      */
6144
6145     return af.alignPanel;
6146   }
6147
6148   private Hashtable jvids2vobj;
6149
6150   private void warn(String msg)
6151   {
6152     warn(msg, null);
6153   }
6154
6155   private void warn(String msg, Exception e)
6156   {
6157     if (Cache.log != null)
6158     {
6159       if (e != null)
6160       {
6161         Cache.log.warn(msg, e);
6162       }
6163       else
6164       {
6165         Cache.log.warn(msg);
6166       }
6167     }
6168     else
6169     {
6170       System.err.println("Warning: " + msg);
6171       if (e != null)
6172       {
6173         e.printStackTrace();
6174       }
6175     }
6176   }
6177
6178   private void debug(String string)
6179   {
6180     debug(string, null);
6181   }
6182
6183   private void debug(String msg, Exception e)
6184   {
6185     if (Cache.log != null)
6186     {
6187       if (e != null)
6188       {
6189         Cache.log.debug(msg, e);
6190       }
6191       else
6192       {
6193         Cache.log.debug(msg);
6194       }
6195     }
6196     else
6197     {
6198       System.err.println("Warning: " + msg);
6199       if (e != null)
6200       {
6201         e.printStackTrace();
6202       }
6203     }
6204   }
6205
6206   /**
6207    * set the object to ID mapping tables used to write/recover objects and XML
6208    * ID strings for the jalview project. If external tables are provided then
6209    * finalize and clearSeqRefs will not clear the tables when the Jalview2XML
6210    * object goes out of scope. - also populates the datasetIds hashtable with
6211    * alignment objects containing dataset sequences
6212    * 
6213    * @param vobj2jv
6214    *          Map from ID strings to jalview datamodel
6215    * @param jv2vobj
6216    *          Map from jalview datamodel to ID strings
6217    * 
6218    * 
6219    */
6220   public void setObjectMappingTables(Hashtable vobj2jv,
6221           IdentityHashMap jv2vobj)
6222   {
6223     this.jv2vobj = jv2vobj;
6224     this.vobj2jv = vobj2jv;
6225     Iterator ds = jv2vobj.keySet().iterator();
6226     String id;
6227     while (ds.hasNext())
6228     {
6229       Object jvobj = ds.next();
6230       id = jv2vobj.get(jvobj).toString();
6231       if (jvobj instanceof jalview.datamodel.Alignment)
6232       {
6233         if (((jalview.datamodel.Alignment) jvobj).getDataset() == null)
6234         {
6235           addDatasetRef(id, (jalview.datamodel.Alignment) jvobj);
6236         }
6237       }
6238       else if (jvobj instanceof jalview.datamodel.Sequence)
6239       {
6240         // register sequence object so the XML parser can recover it.
6241         if (seqRefIds == null)
6242         {
6243           seqRefIds = new HashMap<>();
6244         }
6245         if (seqsToIds == null)
6246         {
6247           seqsToIds = new IdentityHashMap<>();
6248         }
6249         seqRefIds.put(jv2vobj.get(jvobj).toString(), (SequenceI) jvobj);
6250         seqsToIds.put((SequenceI) jvobj, id);
6251       }
6252       else if (jvobj instanceof jalview.datamodel.AlignmentAnnotation)
6253       {
6254         String anid;
6255         AlignmentAnnotation jvann = (AlignmentAnnotation) jvobj;
6256         annotationIds.put(anid = jv2vobj.get(jvobj).toString(), jvann);
6257         if (jvann.annotationId == null)
6258         {
6259           jvann.annotationId = anid;
6260         }
6261         if (!jvann.annotationId.equals(anid))
6262         {
6263           // TODO verify that this is the correct behaviour
6264           this.warn("Overriding Annotation ID for " + anid
6265                   + " from different id : " + jvann.annotationId);
6266           jvann.annotationId = anid;
6267         }
6268       }
6269       else if (jvobj instanceof String)
6270       {
6271         if (jvids2vobj == null)
6272         {
6273           jvids2vobj = new Hashtable();
6274           jvids2vobj.put(jvobj, jv2vobj.get(jvobj).toString());
6275         }
6276       }
6277       else
6278       {
6279         Cache.log.debug("Ignoring " + jvobj.getClass() + " (ID = " + id);
6280       }
6281     }
6282   }
6283
6284   /**
6285    * set the uniqueSetSuffix used to prefix/suffix object IDs for jalview
6286    * objects created from the project archive. If string is null (default for
6287    * construction) then suffix will be set automatically.
6288    * 
6289    * @param string
6290    */
6291   public void setUniqueSetSuffix(String string)
6292   {
6293     uniqueSetSuffix = string;
6294
6295   }
6296
6297   /**
6298    * uses skipList2 as the skipList for skipping views on sequence sets
6299    * associated with keys in the skipList
6300    * 
6301    * @param skipList2
6302    */
6303   public void setSkipList(Hashtable skipList2)
6304   {
6305     skipList = skipList2;
6306   }
6307
6308   /**
6309    * Reads the jar entry of given name and returns its contents, or null if the
6310    * entry is not found.
6311    * 
6312    * @param jprovider
6313    * @param jarEntryName
6314    * @return
6315    */
6316   protected String readJarEntry(jarInputStreamProvider jprovider,
6317           String jarEntryName)
6318   {
6319     String result = null;
6320     BufferedReader in = null;
6321
6322     try
6323     {
6324       /*
6325        * Reopen the jar input stream and traverse its entries to find a matching
6326        * name
6327        */
6328       JarInputStream jin = jprovider.getJarInputStream();
6329       JarEntry entry = null;
6330       do
6331       {
6332         entry = jin.getNextJarEntry();
6333       } while (entry != null && !entry.getName().equals(jarEntryName));
6334
6335       if (entry != null)
6336       {
6337         StringBuilder out = new StringBuilder(256);
6338         in = new BufferedReader(new InputStreamReader(jin, UTF_8));
6339         String data;
6340
6341         while ((data = in.readLine()) != null)
6342         {
6343           out.append(data);
6344         }
6345         result = out.toString();
6346       }
6347       else
6348       {
6349         warn("Couldn't find entry in Jalview Jar for " + jarEntryName);
6350       }
6351     } catch (Exception ex)
6352     {
6353       ex.printStackTrace();
6354     } finally
6355     {
6356       if (in != null)
6357       {
6358         try
6359         {
6360           in.close();
6361         } catch (IOException e)
6362         {
6363           // ignore
6364         }
6365       }
6366     }
6367
6368     return result;
6369   }
6370
6371   /**
6372    * Returns an incrementing counter (0, 1, 2...)
6373    * 
6374    * @return
6375    */
6376   private synchronized int nextCounter()
6377   {
6378     return counter++;
6379   }
6380
6381   /**
6382    * Loads any saved PCA viewers
6383    * 
6384    * @param jms
6385    * @param ap
6386    */
6387   protected void loadPCAViewers(JalviewModel model, AlignmentPanel ap)
6388   {
6389     try
6390     {
6391       List<PcaViewer> pcaviewers = model.getPcaViewer();
6392       for (PcaViewer viewer : pcaviewers)
6393       {
6394         String modelName = viewer.getScoreModelName();
6395         SimilarityParamsI params = new SimilarityParams(
6396                 viewer.isIncludeGappedColumns(), viewer.isMatchGaps(),
6397                 viewer.isIncludeGaps(),
6398                 viewer.isDenominateByShortestLength());
6399
6400         /*
6401          * create the panel (without computing the PCA)
6402          */
6403         PCAPanel panel = new PCAPanel(ap, modelName, params);
6404
6405         panel.setTitle(viewer.getTitle());
6406         panel.setBounds(new Rectangle(viewer.getXpos(), viewer.getYpos(),
6407                 viewer.getWidth(), viewer.getHeight()));
6408
6409         boolean showLabels = viewer.isShowLabels();
6410         panel.setShowLabels(showLabels);
6411         panel.getRotatableCanvas().setShowLabels(showLabels);
6412         panel.getRotatableCanvas()
6413                 .setBgColour(new Color(viewer.getBgColour()));
6414         panel.getRotatableCanvas()
6415                 .setApplyToAllViews(viewer.isLinkToAllViews());
6416
6417         /*
6418          * load PCA output data
6419          */
6420         ScoreModelI scoreModel = ScoreModels.getInstance()
6421                 .getScoreModel(modelName, ap);
6422         PCA pca = new PCA(null, scoreModel, params);
6423         PcaDataType pcaData = viewer.getPcaData();
6424
6425         MatrixI pairwise = loadDoubleMatrix(pcaData.getPairwiseMatrix());
6426         pca.setPairwiseScores(pairwise);
6427
6428         MatrixI triDiag = loadDoubleMatrix(pcaData.getTridiagonalMatrix());
6429         pca.setTridiagonal(triDiag);
6430
6431         MatrixI result = loadDoubleMatrix(pcaData.getEigenMatrix());
6432         pca.setEigenmatrix(result);
6433
6434         panel.getPcaModel().setPCA(pca);
6435
6436         /*
6437          * we haven't saved the input data! (JAL-2647 to do)
6438          */
6439         panel.setInputData(null);
6440
6441         /*
6442          * add the sequence points for the PCA display
6443          */
6444         List<jalview.datamodel.SequencePoint> seqPoints = new ArrayList<>();
6445         for (SequencePoint sp : viewer.getSequencePoint())
6446         {
6447           String seqId = sp.getSequenceRef();
6448           SequenceI seq = seqRefIds.get(seqId);
6449           if (seq == null)
6450           {
6451             throw new IllegalStateException(
6452                     "Unmatched seqref for PCA: " + seqId);
6453           }
6454           Point pt = new Point(sp.getXPos(), sp.getYPos(), sp.getZPos());
6455           jalview.datamodel.SequencePoint seqPoint = new jalview.datamodel.SequencePoint(
6456                   seq, pt);
6457           seqPoints.add(seqPoint);
6458         }
6459         panel.getRotatableCanvas().setPoints(seqPoints, seqPoints.size());
6460
6461         /*
6462          * set min-max ranges and scale after setPoints (which recomputes them)
6463          */
6464         panel.getRotatableCanvas().setScaleFactor(viewer.getScaleFactor());
6465         SeqPointMin spMin = viewer.getSeqPointMin();
6466         float[] min = new float[] { spMin.getXPos(), spMin.getYPos(),
6467             spMin.getZPos() };
6468         SeqPointMax spMax = viewer.getSeqPointMax();
6469         float[] max = new float[] { spMax.getXPos(), spMax.getYPos(),
6470             spMax.getZPos() };
6471         panel.getRotatableCanvas().setSeqMinMax(min, max);
6472
6473         // todo: hold points list in PCAModel only
6474         panel.getPcaModel().setSequencePoints(seqPoints);
6475
6476         panel.setSelectedDimensionIndex(viewer.getXDim(), X);
6477         panel.setSelectedDimensionIndex(viewer.getYDim(), Y);
6478         panel.setSelectedDimensionIndex(viewer.getZDim(), Z);
6479
6480         // is this duplication needed?
6481         panel.setTop(seqPoints.size() - 1);
6482         panel.getPcaModel().setTop(seqPoints.size() - 1);
6483
6484         /*
6485          * add the axes' end points for the display
6486          */
6487         for (int i = 0; i < 3; i++)
6488         {
6489           Axis axis = viewer.getAxis().get(i);
6490           panel.getRotatableCanvas().getAxisEndPoints()[i] = new Point(
6491                   axis.getXPos(), axis.getYPos(), axis.getZPos());
6492         }
6493
6494         Dimension dim = Platform.getDimIfEmbedded(panel, 475, 450);
6495         Desktop.addInternalFrame(panel, MessageManager.formatMessage(
6496                 "label.calc_title", "PCA", modelName), dim.width,
6497                 dim.height);
6498       }
6499     } catch (Exception ex)
6500     {
6501       Cache.log.error("Error loading PCA: " + ex.toString());
6502     }
6503   }
6504
6505   /**
6506    * Populates an XML model of the feature colour scheme for one feature type
6507    * 
6508    * @param featureType
6509    * @param fcol
6510    * @return
6511    */
6512   public static Colour marshalColour(
6513           String featureType, FeatureColourI fcol)
6514   {
6515     Colour col = new Colour();
6516     if (fcol.isSimpleColour())
6517     {
6518       col.setRGB(Format.getHexString(fcol.getColour()));
6519     }
6520     else
6521     {
6522       col.setRGB(Format.getHexString(fcol.getMaxColour()));
6523       col.setMin(fcol.getMin());
6524       col.setMax(fcol.getMax());
6525       col.setMinRGB(jalview.util.Format.getHexString(fcol.getMinColour()));
6526       col.setAutoScale(fcol.isAutoScaled());
6527       col.setThreshold(fcol.getThreshold());
6528       col.setColourByLabel(fcol.isColourByLabel());
6529       col.setThreshType(fcol.isAboveThreshold() ? ThresholdType.ABOVE
6530               : (fcol.isBelowThreshold() ? ThresholdType.BELOW
6531                       : ThresholdType.NONE));
6532       if (fcol.isColourByAttribute())
6533       {
6534         final String[] attName = fcol.getAttributeName();
6535         col.getAttributeName().add(attName[0]);
6536         if (attName.length > 1)
6537         {
6538           col.getAttributeName().add(attName[1]);
6539         }
6540       }
6541       Color noColour = fcol.getNoColour();
6542       if (noColour == null)
6543       {
6544         col.setNoValueColour(NoValueColour.NONE);
6545       }
6546       else if (noColour == fcol.getMaxColour())
6547       {
6548         col.setNoValueColour(NoValueColour.MAX);
6549       }
6550       else
6551       {
6552         col.setNoValueColour(NoValueColour.MIN);
6553       }
6554     }
6555     col.setName(featureType);
6556     return col;
6557   }
6558
6559   /**
6560    * Populates an XML model of the feature filter(s) for one feature type
6561    * 
6562    * @param firstMatcher
6563    *          the first (or only) match condition)
6564    * @param filter
6565    *          remaining match conditions (if any)
6566    * @param and
6567    *          if true, conditions are and-ed, else or-ed
6568    */
6569   public static jalview.xml.binding.jalview.FeatureMatcherSet marshalFilter(
6570           FeatureMatcherI firstMatcher, Iterator<FeatureMatcherI> filters,
6571           boolean and)
6572   {
6573     jalview.xml.binding.jalview.FeatureMatcherSet result = new jalview.xml.binding.jalview.FeatureMatcherSet();
6574   
6575     if (filters.hasNext())
6576     {
6577       /*
6578        * compound matcher
6579        */
6580       CompoundMatcher compound = new CompoundMatcher();
6581       compound.setAnd(and);
6582       jalview.xml.binding.jalview.FeatureMatcherSet matcher1 = marshalFilter(
6583               firstMatcher, Collections.emptyIterator(), and);
6584       // compound.addMatcherSet(matcher1);
6585       compound.getMatcherSet().add(matcher1);
6586       FeatureMatcherI nextMatcher = filters.next();
6587       jalview.xml.binding.jalview.FeatureMatcherSet matcher2 = marshalFilter(
6588               nextMatcher, filters, and);
6589       // compound.addMatcherSet(matcher2);
6590       compound.getMatcherSet().add(matcher2);
6591       result.setCompoundMatcher(compound);
6592     }
6593     else
6594     {
6595       /*
6596        * single condition matcher
6597        */
6598       // MatchCondition matcherModel = new MatchCondition();
6599       jalview.xml.binding.jalview.FeatureMatcher matcherModel = new jalview.xml.binding.jalview.FeatureMatcher();
6600       matcherModel.setCondition(
6601               firstMatcher.getMatcher().getCondition().getStableName());
6602       matcherModel.setValue(firstMatcher.getMatcher().getPattern());
6603       if (firstMatcher.isByAttribute())
6604       {
6605         matcherModel.setBy(FilterBy.BY_ATTRIBUTE);
6606         // matcherModel.setAttributeName(firstMatcher.getAttribute());
6607         String[] attName = firstMatcher.getAttribute();
6608         matcherModel.getAttributeName().add(attName[0]); // attribute
6609         if (attName.length > 1)
6610         {
6611           matcherModel.getAttributeName().add(attName[1]); // sub-attribute
6612         }
6613       }
6614       else if (firstMatcher.isByLabel())
6615       {
6616         matcherModel.setBy(FilterBy.BY_LABEL);
6617       }
6618       else if (firstMatcher.isByScore())
6619       {
6620         matcherModel.setBy(FilterBy.BY_SCORE);
6621       }
6622       result.setMatchCondition(matcherModel);
6623     }
6624   
6625     return result;
6626   }
6627
6628   /**
6629    * Loads one XML model of a feature filter to a Jalview object
6630    * 
6631    * @param featureType
6632    * @param matcherSetModel
6633    * @return
6634    */
6635   public static FeatureMatcherSetI parseFilter(
6636           String featureType,
6637           jalview.xml.binding.jalview.FeatureMatcherSet matcherSetModel)
6638   {
6639     FeatureMatcherSetI result = new FeatureMatcherSet();
6640     try
6641     {
6642       parseFilterConditions(result, matcherSetModel, true);
6643     } catch (IllegalStateException e)
6644     {
6645       // mixing AND and OR conditions perhaps
6646       System.err.println(
6647               String.format("Error reading filter conditions for '%s': %s",
6648                       featureType, e.getMessage()));
6649       // return as much as was parsed up to the error
6650     }
6651   
6652     return result;
6653   }
6654
6655   /**
6656    * Adds feature match conditions to matcherSet as unmarshalled from XML
6657    * (possibly recursively for compound conditions)
6658    * 
6659    * @param matcherSet
6660    * @param matcherSetModel
6661    * @param and
6662    *          if true, multiple conditions are AND-ed, else they are OR-ed
6663    * @throws IllegalStateException
6664    *           if AND and OR conditions are mixed
6665    */
6666   protected static void parseFilterConditions(
6667           FeatureMatcherSetI matcherSet,
6668           jalview.xml.binding.jalview.FeatureMatcherSet matcherSetModel,
6669           boolean and)
6670   {
6671     jalview.xml.binding.jalview.FeatureMatcher mc = matcherSetModel
6672             .getMatchCondition();
6673     if (mc != null)
6674     {
6675       /*
6676        * single condition
6677        */
6678       FilterBy filterBy = mc.getBy();
6679       Condition cond = Condition.fromString(mc.getCondition());
6680       String pattern = mc.getValue();
6681       FeatureMatcherI matchCondition = null;
6682       if (filterBy == FilterBy.BY_LABEL)
6683       {
6684         matchCondition = FeatureMatcher.byLabel(cond, pattern);
6685       }
6686       else if (filterBy == FilterBy.BY_SCORE)
6687       {
6688         matchCondition = FeatureMatcher.byScore(cond, pattern);
6689   
6690       }
6691       else if (filterBy == FilterBy.BY_ATTRIBUTE)
6692       {
6693         final List<String> attributeName = mc.getAttributeName();
6694         String[] attNames = attributeName
6695                 .toArray(new String[attributeName.size()]);
6696         matchCondition = FeatureMatcher.byAttribute(cond, pattern,
6697                 attNames);
6698       }
6699   
6700       /*
6701        * note this throws IllegalStateException if AND-ing to a 
6702        * previously OR-ed compound condition, or vice versa
6703        */
6704       if (and)
6705       {
6706         matcherSet.and(matchCondition);
6707       }
6708       else
6709       {
6710         matcherSet.or(matchCondition);
6711       }
6712     }
6713     else
6714     {
6715       /*
6716        * compound condition
6717        */
6718       List<jalview.xml.binding.jalview.FeatureMatcherSet> matchers = matcherSetModel
6719               .getCompoundMatcher().getMatcherSet();
6720       boolean anded = matcherSetModel.getCompoundMatcher().isAnd();
6721       if (matchers.size() == 2)
6722       {
6723         parseFilterConditions(matcherSet, matchers.get(0), anded);
6724         parseFilterConditions(matcherSet, matchers.get(1), anded);
6725       }
6726       else
6727       {
6728         System.err.println("Malformed compound filter condition");
6729       }
6730     }
6731   }
6732
6733   /**
6734    * Loads one XML model of a feature colour to a Jalview object
6735    * 
6736    * @param colourModel
6737    * @return
6738    */
6739   public static FeatureColourI parseColour(Colour colourModel)
6740   {
6741     FeatureColourI colour = null;
6742   
6743     if (colourModel.getMax() != null)
6744     {
6745       Color mincol = null;
6746       Color maxcol = null;
6747       Color noValueColour = null;
6748   
6749       try
6750       {
6751         mincol = new Color(Integer.parseInt(colourModel.getMinRGB(), 16));
6752         maxcol = new Color(Integer.parseInt(colourModel.getRGB(), 16));
6753       } catch (Exception e)
6754       {
6755         Cache.log.warn("Couldn't parse out graduated feature color.", e);
6756       }
6757   
6758       NoValueColour noCol = colourModel.getNoValueColour();
6759       if (noCol == NoValueColour.MIN)
6760       {
6761         noValueColour = mincol;
6762       }
6763       else if (noCol == NoValueColour.MAX)
6764       {
6765         noValueColour = maxcol;
6766       }
6767   
6768       colour = new FeatureColour(maxcol, mincol, maxcol, noValueColour,
6769               safeFloat(colourModel.getMin()),
6770               safeFloat(colourModel.getMax()));
6771       final List<String> attributeName = colourModel.getAttributeName();
6772       String[] attributes = attributeName
6773               .toArray(new String[attributeName.size()]);
6774       if (attributes != null && attributes.length > 0)
6775       {
6776         colour.setAttributeName(attributes);
6777       }
6778       if (colourModel.isAutoScale() != null)
6779       {
6780         colour.setAutoScaled(colourModel.isAutoScale().booleanValue());
6781       }
6782       if (colourModel.isColourByLabel() != null)
6783       {
6784         colour.setColourByLabel(
6785                 colourModel.isColourByLabel().booleanValue());
6786       }
6787       if (colourModel.getThreshold() != null)
6788       {
6789         colour.setThreshold(colourModel.getThreshold().floatValue());
6790       }
6791       ThresholdType ttyp = colourModel.getThreshType();
6792       if (ttyp == ThresholdType.ABOVE)
6793       {
6794         colour.setAboveThreshold(true);
6795       }
6796       else if (ttyp == ThresholdType.BELOW)
6797       {
6798         colour.setBelowThreshold(true);
6799       }
6800     }
6801     else
6802     {
6803       Color color = new Color(Integer.parseInt(colourModel.getRGB(), 16));
6804       colour = new FeatureColour(color);
6805     }
6806   
6807     return colour;
6808   }
6809 }