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