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