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