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