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