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