Merge branch 'develop' into bug/JAL-4125_flatlaf_quithandler_warning_doesnt_display_p...
[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     // belt-and-braces create a threshold line if the 
5098     // colourscheme needs one but the matchedAnnotation doesn't have one
5099     if (safeInt(viewAnnColour.getAboveThreshold()) != 0
5100             && matchedAnnotation.getThreshold() == null)
5101     {
5102       matchedAnnotation.setThreshold(
5103               new GraphLine(safeFloat(viewAnnColour.getThreshold()),
5104                       "Threshold", Color.black));
5105     }
5106
5107     AnnotationColourGradient cs = null;
5108     if (viewAnnColour.getColourScheme().equals("None"))
5109     {
5110       cs = new AnnotationColourGradient(matchedAnnotation,
5111               new Color(safeInt(viewAnnColour.getMinColour())),
5112               new Color(safeInt(viewAnnColour.getMaxColour())),
5113               safeInt(viewAnnColour.getAboveThreshold()));
5114     }
5115     else if (viewAnnColour.getColourScheme().startsWith("ucs"))
5116     {
5117       cs = new AnnotationColourGradient(matchedAnnotation,
5118               getUserColourScheme(model, viewAnnColour.getColourScheme()),
5119               safeInt(viewAnnColour.getAboveThreshold()));
5120     }
5121     else
5122     {
5123       cs = new AnnotationColourGradient(matchedAnnotation,
5124               ColourSchemeProperty.getColourScheme(af.getViewport(), al,
5125                       viewAnnColour.getColourScheme()),
5126               safeInt(viewAnnColour.getAboveThreshold()));
5127     }
5128
5129     boolean perSequenceOnly = safeBoolean(viewAnnColour.isPerSequence());
5130     boolean useOriginalColours = safeBoolean(
5131             viewAnnColour.isPredefinedColours());
5132     cs.setSeqAssociated(perSequenceOnly);
5133     cs.setPredefinedColours(useOriginalColours);
5134
5135     if (propagateAnnColour && al.getGroups() != null)
5136     {
5137       // Also use these settings for all the groups
5138       for (int g = 0; g < al.getGroups().size(); g++)
5139       {
5140         SequenceGroup sg = al.getGroups().get(g);
5141         if (sg.getGroupColourScheme() == null)
5142         {
5143           continue;
5144         }
5145
5146         AnnotationColourGradient groupScheme = new AnnotationColourGradient(
5147                 matchedAnnotation, sg.getColourScheme(),
5148                 safeInt(viewAnnColour.getAboveThreshold()));
5149         sg.setColourScheme(groupScheme);
5150         groupScheme.setSeqAssociated(perSequenceOnly);
5151         groupScheme.setPredefinedColours(useOriginalColours);
5152       }
5153     }
5154     return cs;
5155   }
5156
5157   private void reorderAutoannotation(AlignFrame af, AlignmentI al,
5158           List<JvAnnotRow> autoAlan)
5159   {
5160     // copy over visualization settings for autocalculated annotation in the
5161     // view
5162     if (al.getAlignmentAnnotation() != null)
5163     {
5164       /**
5165        * Kludge for magic autoannotation names (see JAL-811)
5166        */
5167       String[] magicNames = new String[] { "Consensus", "Quality",
5168           "Conservation" };
5169       JvAnnotRow nullAnnot = new JvAnnotRow(-1, null);
5170       Hashtable<String, JvAnnotRow> visan = new Hashtable<>();
5171       for (String nm : magicNames)
5172       {
5173         visan.put(nm, nullAnnot);
5174       }
5175       for (JvAnnotRow auan : autoAlan)
5176       {
5177         visan.put(auan.template.label
5178                 + (auan.template.getCalcId() == null ? ""
5179                         : "\t" + auan.template.getCalcId()),
5180                 auan);
5181       }
5182       int hSize = al.getAlignmentAnnotation().length;
5183       List<JvAnnotRow> reorder = new ArrayList<>();
5184       // work through any autoCalculated annotation already on the view
5185       // removing it if it should be placed in a different location on the
5186       // annotation panel.
5187       List<String> remains = new ArrayList<>(visan.keySet());
5188       for (int h = 0; h < hSize; h++)
5189       {
5190         jalview.datamodel.AlignmentAnnotation jalan = al
5191                 .getAlignmentAnnotation()[h];
5192         if (jalan.autoCalculated)
5193         {
5194           String k;
5195           JvAnnotRow valan = visan.get(k = jalan.label);
5196           if (jalan.getCalcId() != null)
5197           {
5198             valan = visan.get(k = jalan.label + "\t" + jalan.getCalcId());
5199           }
5200
5201           if (valan != null)
5202           {
5203             // delete the auto calculated row from the alignment
5204             al.deleteAnnotation(jalan, false);
5205             remains.remove(k);
5206             hSize--;
5207             h--;
5208             if (valan != nullAnnot)
5209             {
5210               if (jalan != valan.template)
5211               {
5212                 // newly created autoannotation row instance
5213                 // so keep a reference to the visible annotation row
5214                 // and copy over all relevant attributes
5215                 if (valan.template.graphHeight >= 0)
5216
5217                 {
5218                   jalan.graphHeight = valan.template.graphHeight;
5219                 }
5220                 jalan.visible = valan.template.visible;
5221               }
5222               reorder.add(new JvAnnotRow(valan.order, jalan));
5223             }
5224           }
5225         }
5226       }
5227       // Add any (possibly stale) autocalculated rows that were not appended to
5228       // the view during construction
5229       for (String other : remains)
5230       {
5231         JvAnnotRow othera = visan.get(other);
5232         if (othera != nullAnnot && othera.template.getCalcId() != null
5233                 && othera.template.getCalcId().length() > 0)
5234         {
5235           reorder.add(othera);
5236         }
5237       }
5238       // now put the automatic annotation in its correct place
5239       int s = 0, srt[] = new int[reorder.size()];
5240       JvAnnotRow[] rws = new JvAnnotRow[reorder.size()];
5241       for (JvAnnotRow jvar : reorder)
5242       {
5243         rws[s] = jvar;
5244         srt[s++] = jvar.order;
5245       }
5246       reorder.clear();
5247       jalview.util.QuickSort.sort(srt, rws);
5248       // and re-insert the annotation at its correct position
5249       for (JvAnnotRow jvar : rws)
5250       {
5251         al.addAnnotation(jvar.template, jvar.order);
5252       }
5253       af.alignPanel.adjustAnnotationHeight();
5254     }
5255   }
5256
5257   Hashtable skipList = null;
5258
5259   /**
5260    * TODO remove this method
5261    * 
5262    * @param view
5263    * @return AlignFrame bound to sequenceSetId from view, if one exists. private
5264    *         AlignFrame getSkippedFrame(Viewport view) { if (skipList==null) {
5265    *         throw new Error("Implementation Error. No skipList defined for this
5266    *         Jalview2XML instance."); } return (AlignFrame)
5267    *         skipList.get(view.getSequenceSetId()); }
5268    */
5269
5270   /**
5271    * Check if the Jalview view contained in object should be skipped or not.
5272    * 
5273    * @param object
5274    * @return true if view's sequenceSetId is a key in skipList
5275    */
5276   private boolean skipViewport(JalviewModel object)
5277   {
5278     if (skipList == null)
5279     {
5280       return false;
5281     }
5282     String id = object.getViewport().get(0).getSequenceSetId();
5283     if (skipList.containsKey(id))
5284     {
5285       Console.debug("Skipping seuqence set id " + id);
5286       return true;
5287     }
5288     return false;
5289   }
5290
5291   public void addToSkipList(AlignFrame af)
5292   {
5293     if (skipList == null)
5294     {
5295       skipList = new Hashtable();
5296     }
5297     skipList.put(af.getViewport().getSequenceSetId(), af);
5298   }
5299
5300   public void clearSkipList()
5301   {
5302     if (skipList != null)
5303     {
5304       skipList.clear();
5305       skipList = null;
5306     }
5307   }
5308
5309   private void recoverDatasetFor(SequenceSet vamsasSet, AlignmentI al,
5310           boolean ignoreUnrefed, String uniqueSeqSetId)
5311   {
5312     jalview.datamodel.AlignmentI ds = getDatasetFor(
5313             vamsasSet.getDatasetId());
5314     AlignmentI xtant_ds = ds;
5315     if (xtant_ds == null)
5316     {
5317       // good chance we are about to create a new dataset, but check if we've
5318       // seen some of the dataset sequence IDs before.
5319       // TODO: skip this check if we are working with project generated by
5320       // version 2.11 or later
5321       xtant_ds = checkIfHasDataset(vamsasSet.getSequence());
5322       if (xtant_ds != null)
5323       {
5324         ds = xtant_ds;
5325         addDatasetRef(vamsasSet.getDatasetId(), ds);
5326       }
5327     }
5328     Vector<SequenceI> dseqs = null;
5329     if (!ignoreUnrefed)
5330     {
5331       // recovering an alignment View
5332       AlignmentI seqSetDS = getDatasetFor(UNIQSEQSETID + uniqueSeqSetId);
5333       if (seqSetDS != null)
5334       {
5335         if (ds != null && ds != seqSetDS)
5336         {
5337           Console.warn(
5338                   "JAL-3171 regression: Overwriting a dataset reference for an alignment"
5339                           + " - CDS/Protein crossreference data may be lost");
5340           if (xtant_ds != null)
5341           {
5342             // This can only happen if the unique sequence set ID was bound to a
5343             // dataset that did not contain any of the sequences in the view
5344             // currently being restored.
5345             Console.warn(
5346                     "JAL-3171 SERIOUS!  TOTAL CONFUSION - please consider contacting the Jalview Development team so they can investigate why your project caused this message to be displayed.");
5347           }
5348         }
5349         ds = seqSetDS;
5350         addDatasetRef(vamsasSet.getDatasetId(), ds);
5351       }
5352     }
5353     if (ds == null)
5354     {
5355       // try even harder to restore dataset
5356       AlignmentI xtantDS = checkIfHasDataset(vamsasSet.getSequence());
5357       // create a list of new dataset sequences
5358       dseqs = new Vector<>();
5359     }
5360     for (int i = 0, iSize = vamsasSet.getSequence().size(); i < iSize; i++)
5361     {
5362       Sequence vamsasSeq = vamsasSet.getSequence().get(i);
5363       ensureJalviewDatasetSequence(vamsasSeq, ds, dseqs, ignoreUnrefed, i);
5364     }
5365     // create a new dataset
5366     if (ds == null)
5367     {
5368       SequenceI[] dsseqs = new SequenceI[dseqs.size()];
5369       dseqs.copyInto(dsseqs);
5370       ds = new jalview.datamodel.Alignment(dsseqs);
5371       Console.debug("Created new dataset " + vamsasSet.getDatasetId()
5372               + " for alignment " + System.identityHashCode(al));
5373       addDatasetRef(vamsasSet.getDatasetId(), ds);
5374     }
5375     // set the dataset for the newly imported alignment.
5376     if (al.getDataset() == null && !ignoreUnrefed)
5377     {
5378       al.setDataset(ds);
5379       // register dataset for the alignment's uniqueSeqSetId for legacy projects
5380       addDatasetRef(UNIQSEQSETID + uniqueSeqSetId, ds);
5381     }
5382     updateSeqDatasetBinding(vamsasSet.getSequence(), ds);
5383   }
5384
5385   /**
5386    * XML dataset sequence ID to materialised dataset reference
5387    */
5388   HashMap<String, AlignmentI> seqToDataset = new HashMap<>();
5389
5390   /**
5391    * @return the first materialised dataset reference containing a dataset
5392    *         sequence referenced in the given view
5393    * @param list
5394    *          - sequences from the view
5395    */
5396   AlignmentI checkIfHasDataset(List<Sequence> list)
5397   {
5398     for (Sequence restoredSeq : list)
5399     {
5400       AlignmentI datasetFor = seqToDataset.get(restoredSeq.getDsseqid());
5401       if (datasetFor != null)
5402       {
5403         return datasetFor;
5404       }
5405     }
5406     return null;
5407   }
5408
5409   /**
5410    * Register ds as the containing dataset for the dataset sequences referenced
5411    * by sequences in list
5412    * 
5413    * @param list
5414    *          - sequences in a view
5415    * @param ds
5416    */
5417   void updateSeqDatasetBinding(List<Sequence> list, AlignmentI ds)
5418   {
5419     for (Sequence restoredSeq : list)
5420     {
5421       AlignmentI prevDS = seqToDataset.put(restoredSeq.getDsseqid(), ds);
5422       if (prevDS != null && prevDS != ds)
5423       {
5424         Console.warn("Dataset sequence appears in many datasets: "
5425                 + restoredSeq.getDsseqid());
5426         // TODO: try to merge!
5427       }
5428     }
5429   }
5430
5431   /**
5432    * 
5433    * @param vamsasSeq
5434    *          sequence definition to create/merge dataset sequence for
5435    * @param ds
5436    *          dataset alignment
5437    * @param dseqs
5438    *          vector to add new dataset sequence to
5439    * @param ignoreUnrefed
5440    *          - when true, don't create new sequences from vamsasSeq if it's id
5441    *          doesn't already have an asssociated Jalview sequence.
5442    * @param vseqpos
5443    *          - used to reorder the sequence in the alignment according to the
5444    *          vamsasSeq array ordering, to preserve ordering of dataset
5445    */
5446   private void ensureJalviewDatasetSequence(Sequence vamsasSeq,
5447           AlignmentI ds, Vector<SequenceI> dseqs, boolean ignoreUnrefed,
5448           int vseqpos)
5449   {
5450     // JBP TODO: Check this is called for AlCodonFrames to support recovery of
5451     // xRef Codon Maps
5452     SequenceI sq = seqRefIds.get(vamsasSeq.getId());
5453     boolean reorder = false;
5454     SequenceI dsq = null;
5455     if (sq != null && sq.getDatasetSequence() != null)
5456     {
5457       dsq = sq.getDatasetSequence();
5458     }
5459     else
5460     {
5461       reorder = true;
5462     }
5463     if (sq == null && ignoreUnrefed)
5464     {
5465       return;
5466     }
5467     String sqid = vamsasSeq.getDsseqid();
5468     if (dsq == null)
5469     {
5470       // need to create or add a new dataset sequence reference to this sequence
5471       if (sqid != null)
5472       {
5473         dsq = seqRefIds.get(sqid);
5474       }
5475       // check again
5476       if (dsq == null)
5477       {
5478         // make a new dataset sequence
5479         dsq = sq.createDatasetSequence();
5480         if (sqid == null)
5481         {
5482           // make up a new dataset reference for this sequence
5483           sqid = seqHash(dsq);
5484         }
5485         dsq.setVamsasId(uniqueSetSuffix + sqid);
5486         seqRefIds.put(sqid, dsq);
5487         if (ds == null)
5488         {
5489           if (dseqs != null)
5490           {
5491             dseqs.addElement(dsq);
5492           }
5493         }
5494         else
5495         {
5496           ds.addSequence(dsq);
5497         }
5498       }
5499       else
5500       {
5501         if (sq != dsq)
5502         { // make this dataset sequence sq's dataset sequence
5503           sq.setDatasetSequence(dsq);
5504           // and update the current dataset alignment
5505           if (ds == null)
5506           {
5507             if (dseqs != null)
5508             {
5509               if (!dseqs.contains(dsq))
5510               {
5511                 dseqs.add(dsq);
5512               }
5513             }
5514             else
5515             {
5516               if (ds.findIndex(dsq) < 0)
5517               {
5518                 ds.addSequence(dsq);
5519               }
5520             }
5521           }
5522         }
5523       }
5524     }
5525     // TODO: refactor this as a merge dataset sequence function
5526     // now check that sq (the dataset sequence) sequence really is the union of
5527     // all references to it
5528     // boolean pre = sq.getStart() < dsq.getStart();
5529     // boolean post = sq.getEnd() > dsq.getEnd();
5530     // if (pre || post)
5531     if (sq != dsq)
5532     {
5533       // StringBuffer sb = new StringBuffer();
5534       String newres = jalview.analysis.AlignSeq.extractGaps(
5535               jalview.util.Comparison.GapChars, sq.getSequenceAsString());
5536       if (!newres.equalsIgnoreCase(dsq.getSequenceAsString())
5537               && newres.length() > dsq.getLength())
5538       {
5539         // Update with the longer sequence.
5540         synchronized (dsq)
5541         {
5542           /*
5543            * if (pre) { sb.insert(0, newres .substring(0, dsq.getStart() -
5544            * sq.getStart())); dsq.setStart(sq.getStart()); } if (post) {
5545            * sb.append(newres.substring(newres.length() - sq.getEnd() -
5546            * dsq.getEnd())); dsq.setEnd(sq.getEnd()); }
5547            */
5548           dsq.setSequence(newres);
5549         }
5550         // TODO: merges will never happen if we 'know' we have the real dataset
5551         // sequence - this should be detected when id==dssid
5552         System.err.println(
5553                 "DEBUG Notice:  Merged dataset sequence (if you see this often, post at http://issues.jalview.org/browse/JAL-1474)"); // ("
5554         // + (pre ? "prepended" : "") + " "
5555         // + (post ? "appended" : ""));
5556       }
5557     }
5558     else
5559     {
5560       // sequence refs are identical. We may need to update the existing dataset
5561       // alignment with this one, though.
5562       if (ds != null && dseqs == null)
5563       {
5564         int opos = ds.findIndex(dsq);
5565         SequenceI tseq = null;
5566         if (opos != -1 && vseqpos != opos)
5567         {
5568           // remove from old position
5569           ds.deleteSequence(dsq);
5570         }
5571         if (vseqpos < ds.getHeight())
5572         {
5573           if (vseqpos != opos)
5574           {
5575             // save sequence at destination position
5576             tseq = ds.getSequenceAt(vseqpos);
5577             ds.replaceSequenceAt(vseqpos, dsq);
5578             ds.addSequence(tseq);
5579           }
5580         }
5581         else
5582         {
5583           ds.addSequence(dsq);
5584         }
5585       }
5586     }
5587   }
5588
5589   /*
5590    * TODO use AlignmentI here and in related methods - needs
5591    * AlignmentI.getDataset() changed to return AlignmentI instead of Alignment
5592    */
5593   Hashtable<String, AlignmentI> datasetIds = null;
5594
5595   IdentityHashMap<AlignmentI, String> dataset2Ids = null;
5596
5597   private AlignmentI getDatasetFor(String datasetId)
5598   {
5599     if (datasetIds == null)
5600     {
5601       datasetIds = new Hashtable<>();
5602       return null;
5603     }
5604     if (datasetIds.containsKey(datasetId))
5605     {
5606       return datasetIds.get(datasetId);
5607     }
5608     return null;
5609   }
5610
5611   private void addDatasetRef(String datasetId, AlignmentI dataset)
5612   {
5613     if (datasetIds == null)
5614     {
5615       datasetIds = new Hashtable<>();
5616     }
5617     datasetIds.put(datasetId, dataset);
5618   }
5619
5620   /**
5621    * make a new dataset ID for this jalview dataset alignment
5622    * 
5623    * @param dataset
5624    * @return
5625    */
5626   private String getDatasetIdRef(AlignmentI dataset)
5627   {
5628     if (dataset.getDataset() != null)
5629     {
5630       Console.warn(
5631               "Serious issue!  Dataset Object passed to getDatasetIdRef is not a Jalview DATASET alignment...");
5632     }
5633     String datasetId = makeHashCode(dataset, null);
5634     if (datasetId == null)
5635     {
5636       // make a new datasetId and record it
5637       if (dataset2Ids == null)
5638       {
5639         dataset2Ids = new IdentityHashMap<>();
5640       }
5641       else
5642       {
5643         datasetId = dataset2Ids.get(dataset);
5644       }
5645       if (datasetId == null)
5646       {
5647         datasetId = "ds" + dataset2Ids.size() + 1;
5648         dataset2Ids.put(dataset, datasetId);
5649       }
5650     }
5651     return datasetId;
5652   }
5653
5654   /**
5655    * Add any saved DBRefEntry's to the sequence. An entry flagged as 'locus' is
5656    * constructed as a special subclass GeneLocus.
5657    * 
5658    * @param datasetSequence
5659    * @param sequence
5660    */
5661   private void addDBRefs(SequenceI datasetSequence, Sequence sequence)
5662   {
5663     for (int d = 0; d < sequence.getDBRef().size(); d++)
5664     {
5665       DBRef dr = sequence.getDBRef().get(d);
5666       DBRefEntry entry;
5667       if (dr.isLocus())
5668       {
5669         entry = new GeneLocus(dr.getSource(), dr.getVersion(),
5670                 dr.getAccessionId());
5671       }
5672       else
5673       {
5674         entry = new DBRefEntry(dr.getSource(), dr.getVersion(),
5675                 dr.getAccessionId());
5676       }
5677       if (dr.getMapping() != null)
5678       {
5679         entry.setMap(addMapping(dr.getMapping()));
5680       }
5681       entry.setCanonical(dr.isCanonical());
5682       datasetSequence.addDBRef(entry);
5683     }
5684   }
5685
5686   private jalview.datamodel.Mapping addMapping(Mapping m)
5687   {
5688     SequenceI dsto = null;
5689     // Mapping m = dr.getMapping();
5690     int fr[] = new int[m.getMapListFrom().size() * 2];
5691     Iterator<MapListFrom> from = m.getMapListFrom().iterator();// enumerateMapListFrom();
5692     for (int _i = 0; from.hasNext(); _i += 2)
5693     {
5694       MapListFrom mf = from.next();
5695       fr[_i] = mf.getStart();
5696       fr[_i + 1] = mf.getEnd();
5697     }
5698     int fto[] = new int[m.getMapListTo().size() * 2];
5699     Iterator<MapListTo> to = m.getMapListTo().iterator();// enumerateMapListTo();
5700     for (int _i = 0; to.hasNext(); _i += 2)
5701     {
5702       MapListTo mf = to.next();
5703       fto[_i] = mf.getStart();
5704       fto[_i + 1] = mf.getEnd();
5705     }
5706     jalview.datamodel.Mapping jmap = new jalview.datamodel.Mapping(dsto, fr,
5707             fto, m.getMapFromUnit().intValue(),
5708             m.getMapToUnit().intValue());
5709
5710     /*
5711      * (optional) choice of dseqFor or Sequence
5712      */
5713     if (m.getDseqFor() != null)
5714     {
5715       String dsfor = m.getDseqFor();
5716       if (seqRefIds.containsKey(dsfor))
5717       {
5718         /*
5719          * recover from hash
5720          */
5721         jmap.setTo(seqRefIds.get(dsfor));
5722       }
5723       else
5724       {
5725         frefedSequence.add(newMappingRef(dsfor, jmap));
5726       }
5727     }
5728     else if (m.getSequence() != null)
5729     {
5730       /*
5731        * local sequence definition
5732        */
5733       Sequence ms = m.getSequence();
5734       SequenceI djs = null;
5735       String sqid = ms.getDsseqid();
5736       if (sqid != null && sqid.length() > 0)
5737       {
5738         /*
5739          * recover dataset sequence
5740          */
5741         djs = seqRefIds.get(sqid);
5742       }
5743       else
5744       {
5745         System.err.println(
5746                 "Warning - making up dataset sequence id for DbRef sequence map reference");
5747         sqid = ((Object) ms).toString(); // make up a new hascode for
5748         // undefined dataset sequence hash
5749         // (unlikely to happen)
5750       }
5751
5752       if (djs == null)
5753       {
5754         /**
5755          * make a new dataset sequence and add it to refIds hash
5756          */
5757         djs = new jalview.datamodel.Sequence(ms.getName(),
5758                 ms.getSequence());
5759         djs.setStart(jmap.getMap().getToLowest());
5760         djs.setEnd(jmap.getMap().getToHighest());
5761         djs.setVamsasId(uniqueSetSuffix + sqid);
5762         jmap.setTo(djs);
5763         incompleteSeqs.put(sqid, djs);
5764         seqRefIds.put(sqid, djs);
5765
5766       }
5767       Console.debug("about to recurse on addDBRefs.");
5768       addDBRefs(djs, ms);
5769
5770     }
5771
5772     return jmap;
5773   }
5774
5775   /**
5776    * Provides a 'copy' of an alignment view (on action New View) by 'saving' the
5777    * view as XML (but not to file), and then reloading it
5778    * 
5779    * @param ap
5780    * @return
5781    */
5782   public AlignmentPanel copyAlignPanel(AlignmentPanel ap)
5783   {
5784     initSeqRefs();
5785     JalviewModel jm = saveState(ap, null, null, null);
5786
5787     addDatasetRef(
5788             jm.getVamsasModel().getSequenceSet().get(0).getDatasetId(),
5789             ap.getAlignment().getDataset());
5790
5791     uniqueSetSuffix = "";
5792     // jm.getJalviewModelSequence().getViewport(0).setId(null);
5793     jm.getViewport().get(0).setId(null);
5794     // we don't overwrite the view we just copied
5795
5796     if (this.frefedSequence == null)
5797     {
5798       frefedSequence = new Vector<>();
5799     }
5800
5801     viewportsAdded.clear();
5802
5803     AlignFrame af = loadFromObject(jm, null, false, null);
5804     af.getAlignPanels().clear();
5805     af.closeMenuItem_actionPerformed(true);
5806
5807     /*
5808      * if(ap.av.getAlignment().getAlignmentAnnotation()!=null) { for(int i=0;
5809      * i<ap.av.getAlignment().getAlignmentAnnotation().length; i++) {
5810      * if(!ap.av.getAlignment().getAlignmentAnnotation()[i].autoCalculated) {
5811      * af.alignPanel.av.getAlignment().getAlignmentAnnotation()[i] =
5812      * ap.av.getAlignment().getAlignmentAnnotation()[i]; } } }
5813      */
5814
5815     return af.alignPanel;
5816   }
5817
5818   private Hashtable jvids2vobj;
5819
5820   /**
5821    * set the object to ID mapping tables used to write/recover objects and XML
5822    * ID strings for the jalview project. If external tables are provided then
5823    * finalize and clearSeqRefs will not clear the tables when the Jalview2XML
5824    * object goes out of scope. - also populates the datasetIds hashtable with
5825    * alignment objects containing dataset sequences
5826    * 
5827    * @param vobj2jv
5828    *          Map from ID strings to jalview datamodel
5829    * @param jv2vobj
5830    *          Map from jalview datamodel to ID strings
5831    * 
5832    * 
5833    */
5834   public void setObjectMappingTables(Hashtable vobj2jv,
5835           IdentityHashMap jv2vobj)
5836   {
5837     this.jv2vobj = jv2vobj;
5838     this.vobj2jv = vobj2jv;
5839     Iterator ds = jv2vobj.keySet().iterator();
5840     String id;
5841     while (ds.hasNext())
5842     {
5843       Object jvobj = ds.next();
5844       id = jv2vobj.get(jvobj).toString();
5845       if (jvobj instanceof jalview.datamodel.Alignment)
5846       {
5847         if (((jalview.datamodel.Alignment) jvobj).getDataset() == null)
5848         {
5849           addDatasetRef(id, (jalview.datamodel.Alignment) jvobj);
5850         }
5851       }
5852       else if (jvobj instanceof jalview.datamodel.Sequence)
5853       {
5854         // register sequence object so the XML parser can recover it.
5855         if (seqRefIds == null)
5856         {
5857           seqRefIds = new HashMap<>();
5858         }
5859         if (seqsToIds == null)
5860         {
5861           seqsToIds = new IdentityHashMap<>();
5862         }
5863         seqRefIds.put(jv2vobj.get(jvobj).toString(), (SequenceI) jvobj);
5864         seqsToIds.put((SequenceI) jvobj, id);
5865       }
5866       else if (jvobj instanceof jalview.datamodel.AlignmentAnnotation)
5867       {
5868         String anid;
5869         AlignmentAnnotation jvann = (AlignmentAnnotation) jvobj;
5870         annotationIds.put(anid = jv2vobj.get(jvobj).toString(), jvann);
5871         if (jvann.annotationId == null)
5872         {
5873           jvann.annotationId = anid;
5874         }
5875         if (!jvann.annotationId.equals(anid))
5876         {
5877           // TODO verify that this is the correct behaviour
5878           Console.warn("Overriding Annotation ID for " + anid
5879                   + " from different id : " + jvann.annotationId);
5880           jvann.annotationId = anid;
5881         }
5882       }
5883       else if (jvobj instanceof String)
5884       {
5885         if (jvids2vobj == null)
5886         {
5887           jvids2vobj = new Hashtable();
5888           jvids2vobj.put(jvobj, jv2vobj.get(jvobj).toString());
5889         }
5890       }
5891       else
5892       {
5893         Console.debug("Ignoring " + jvobj.getClass() + " (ID = " + id);
5894       }
5895     }
5896   }
5897
5898   /**
5899    * set the uniqueSetSuffix used to prefix/suffix object IDs for jalview
5900    * objects created from the project archive. If string is null (default for
5901    * construction) then suffix will be set automatically.
5902    * 
5903    * @param string
5904    */
5905   public void setUniqueSetSuffix(String string)
5906   {
5907     uniqueSetSuffix = string;
5908
5909   }
5910
5911   /**
5912    * uses skipList2 as the skipList for skipping views on sequence sets
5913    * associated with keys in the skipList
5914    * 
5915    * @param skipList2
5916    */
5917   public void setSkipList(Hashtable skipList2)
5918   {
5919     skipList = skipList2;
5920   }
5921
5922   /**
5923    * Reads the jar entry of given name and returns its contents, or null if the
5924    * entry is not found.
5925    * 
5926    * @param jprovider
5927    * @param jarEntryName
5928    * @return
5929    */
5930   protected String readJarEntry(jarInputStreamProvider jprovider,
5931           String jarEntryName)
5932   {
5933     String result = null;
5934     BufferedReader in = null;
5935
5936     try
5937     {
5938       /*
5939        * Reopen the jar input stream and traverse its entries to find a matching
5940        * name
5941        */
5942       JarInputStream jin = jprovider.getJarInputStream();
5943       JarEntry entry = null;
5944       do
5945       {
5946         entry = jin.getNextJarEntry();
5947       } while (entry != null && !entry.getName().equals(jarEntryName));
5948
5949       if (entry != null)
5950       {
5951         StringBuilder out = new StringBuilder(256);
5952         in = new BufferedReader(new InputStreamReader(jin, UTF_8));
5953         String data;
5954
5955         while ((data = in.readLine()) != null)
5956         {
5957           out.append(data);
5958         }
5959         result = out.toString();
5960       }
5961       else
5962       {
5963         Console.warn(
5964                 "Couldn't find entry in Jalview Jar for " + jarEntryName);
5965       }
5966     } catch (Exception ex)
5967     {
5968       ex.printStackTrace();
5969     } finally
5970     {
5971       if (in != null)
5972       {
5973         try
5974         {
5975           in.close();
5976         } catch (IOException e)
5977         {
5978           // ignore
5979         }
5980       }
5981     }
5982
5983     return result;
5984   }
5985
5986   /**
5987    * Returns an incrementing counter (0, 1, 2...)
5988    * 
5989    * @return
5990    */
5991   private synchronized int nextCounter()
5992   {
5993     return counter++;
5994   }
5995
5996   /**
5997    * Loads any saved PCA viewers
5998    * 
5999    * @param jms
6000    * @param ap
6001    */
6002   protected void loadPCAViewers(JalviewModel model, AlignmentPanel ap)
6003   {
6004     try
6005     {
6006       List<PcaViewer> pcaviewers = model.getPcaViewer();
6007       for (PcaViewer viewer : pcaviewers)
6008       {
6009         String modelName = viewer.getScoreModelName();
6010         SimilarityParamsI params = new SimilarityParams(
6011                 viewer.isIncludeGappedColumns(), viewer.isMatchGaps(),
6012                 viewer.isIncludeGaps(),
6013                 viewer.isDenominateByShortestLength());
6014
6015         /*
6016          * create the panel (without computing the PCA)
6017          */
6018         PCAPanel panel = new PCAPanel(ap, modelName, params);
6019
6020         panel.setTitle(viewer.getTitle());
6021         panel.setBounds(new Rectangle(viewer.getXpos(), viewer.getYpos(),
6022                 viewer.getWidth(), viewer.getHeight()));
6023
6024         boolean showLabels = viewer.isShowLabels();
6025         panel.setShowLabels(showLabels);
6026         panel.getRotatableCanvas().setShowLabels(showLabels);
6027         panel.getRotatableCanvas()
6028                 .setBgColour(new Color(viewer.getBgColour()));
6029         panel.getRotatableCanvas()
6030                 .setApplyToAllViews(viewer.isLinkToAllViews());
6031
6032         /*
6033          * load PCA output data
6034          */
6035         ScoreModelI scoreModel = ScoreModels.getInstance()
6036                 .getScoreModel(modelName, ap);
6037         PCA pca = new PCA(null, scoreModel, params);
6038         PcaDataType pcaData = viewer.getPcaData();
6039
6040         MatrixI pairwise = loadDoubleMatrix(pcaData.getPairwiseMatrix());
6041         pca.setPairwiseScores(pairwise);
6042
6043         MatrixI triDiag = loadDoubleMatrix(pcaData.getTridiagonalMatrix());
6044         pca.setTridiagonal(triDiag);
6045
6046         MatrixI result = loadDoubleMatrix(pcaData.getEigenMatrix());
6047         pca.setEigenmatrix(result);
6048
6049         panel.getPcaModel().setPCA(pca);
6050
6051         /*
6052          * we haven't saved the input data! (JAL-2647 to do)
6053          */
6054         panel.setInputData(null);
6055
6056         /*
6057          * add the sequence points for the PCA display
6058          */
6059         List<jalview.datamodel.SequencePoint> seqPoints = new ArrayList<>();
6060         for (SequencePoint sp : viewer.getSequencePoint())
6061         {
6062           String seqId = sp.getSequenceRef();
6063           SequenceI seq = seqRefIds.get(seqId);
6064           if (seq == null)
6065           {
6066             throw new IllegalStateException(
6067                     "Unmatched seqref for PCA: " + seqId);
6068           }
6069           Point pt = new Point(sp.getXPos(), sp.getYPos(), sp.getZPos());
6070           jalview.datamodel.SequencePoint seqPoint = new jalview.datamodel.SequencePoint(
6071                   seq, pt);
6072           seqPoints.add(seqPoint);
6073         }
6074         panel.getRotatableCanvas().setPoints(seqPoints, seqPoints.size());
6075
6076         /*
6077          * set min-max ranges and scale after setPoints (which recomputes them)
6078          */
6079         panel.getRotatableCanvas().setScaleFactor(viewer.getScaleFactor());
6080         SeqPointMin spMin = viewer.getSeqPointMin();
6081         float[] min = new float[] { spMin.getXPos(), spMin.getYPos(),
6082             spMin.getZPos() };
6083         SeqPointMax spMax = viewer.getSeqPointMax();
6084         float[] max = new float[] { spMax.getXPos(), spMax.getYPos(),
6085             spMax.getZPos() };
6086         panel.getRotatableCanvas().setSeqMinMax(min, max);
6087
6088         // todo: hold points list in PCAModel only
6089         panel.getPcaModel().setSequencePoints(seqPoints);
6090
6091         panel.setSelectedDimensionIndex(viewer.getXDim(), X);
6092         panel.setSelectedDimensionIndex(viewer.getYDim(), Y);
6093         panel.setSelectedDimensionIndex(viewer.getZDim(), Z);
6094
6095         // is this duplication needed?
6096         panel.setTop(seqPoints.size() - 1);
6097         panel.getPcaModel().setTop(seqPoints.size() - 1);
6098
6099         /*
6100          * add the axes' end points for the display
6101          */
6102         for (int i = 0; i < 3; i++)
6103         {
6104           Axis axis = viewer.getAxis().get(i);
6105           panel.getRotatableCanvas().getAxisEndPoints()[i] = new Point(
6106                   axis.getXPos(), axis.getYPos(), axis.getZPos());
6107         }
6108
6109         Desktop.addInternalFrame(panel, MessageManager.formatMessage(
6110                 "label.calc_title", "PCA", modelName), 475, 450);
6111       }
6112     } catch (Exception ex)
6113     {
6114       Console.error("Error loading PCA: " + ex.toString());
6115     }
6116   }
6117
6118   /**
6119    * Creates a new structure viewer window
6120    * 
6121    * @param viewerType
6122    * @param viewerData
6123    * @param af
6124    * @param jprovider
6125    */
6126   protected void createStructureViewer(ViewerType viewerType,
6127           final Entry<String, StructureViewerModel> viewerData,
6128           AlignFrame af, jarInputStreamProvider jprovider)
6129   {
6130     final StructureViewerModel viewerModel = viewerData.getValue();
6131     String sessionFilePath = null;
6132
6133     if (viewerType == ViewerType.JMOL)
6134     {
6135       sessionFilePath = rewriteJmolSession(viewerModel, jprovider);
6136     }
6137     else
6138     {
6139       String viewerJarEntryName = getViewerJarEntryName(
6140               viewerModel.getViewId());
6141       sessionFilePath = copyJarEntry(jprovider, viewerJarEntryName,
6142               "viewerSession", ".tmp");
6143     }
6144     final String sessionPath = sessionFilePath;
6145     final String sviewid = viewerData.getKey();
6146     try
6147     {
6148       SwingUtilities.invokeAndWait(new Runnable()
6149       {
6150         @Override
6151         public void run()
6152         {
6153           JalviewStructureDisplayI sview = null;
6154           try
6155           {
6156             sview = StructureViewer.createView(viewerType, af.alignPanel,
6157                     viewerModel, sessionPath, sviewid);
6158             addNewStructureViewer(sview);
6159           } catch (OutOfMemoryError ex)
6160           {
6161             new OOMWarning("Restoring structure view for " + viewerType,
6162                     (OutOfMemoryError) ex.getCause());
6163             if (sview != null && sview.isVisible())
6164             {
6165               sview.closeViewer(false);
6166               sview.setVisible(false);
6167               sview.dispose();
6168             }
6169           }
6170         }
6171       });
6172     } catch (InvocationTargetException | InterruptedException ex)
6173     {
6174       Console.warn("Unexpected error when opening " + viewerType
6175               + " structure viewer", ex);
6176     }
6177   }
6178
6179   /**
6180    * Rewrites a Jmol session script, saves it to a temporary file, and returns
6181    * the path of the file. "load file" commands are rewritten to change the
6182    * original PDB file names to those created as the Jalview project is loaded.
6183    * 
6184    * @param svattrib
6185    * @param jprovider
6186    * @return
6187    */
6188   private String rewriteJmolSession(StructureViewerModel svattrib,
6189           jarInputStreamProvider jprovider)
6190   {
6191     String state = svattrib.getStateData(); // Jalview < 2.9
6192     if (state == null || state.isEmpty()) // Jalview >= 2.9
6193     {
6194       String jarEntryName = getViewerJarEntryName(svattrib.getViewId());
6195       state = readJarEntry(jprovider, jarEntryName);
6196     }
6197     // TODO or simpler? for each key in oldFiles,
6198     // replace key.getPath() in state with oldFiles.get(key).getFilePath()
6199     // (allowing for different path escapings)
6200     StringBuilder rewritten = new StringBuilder(state.length());
6201     int cp = 0, ncp, ecp;
6202     Map<File, StructureData> oldFiles = svattrib.getFileData();
6203     while ((ncp = state.indexOf("load ", cp)) > -1)
6204     {
6205       do
6206       {
6207         // look for next filename in load statement
6208         rewritten.append(state.substring(cp,
6209                 ncp = (state.indexOf("\"", ncp + 1) + 1)));
6210         String oldfilenam = state.substring(ncp,
6211                 ecp = state.indexOf("\"", ncp));
6212         // recover the new mapping data for this old filename
6213         // have to normalize filename - since Jmol and jalview do
6214         // filename translation differently.
6215         StructureData filedat = oldFiles.get(new File(oldfilenam));
6216         if (filedat == null)
6217         {
6218           String reformatedOldFilename = oldfilenam.replaceAll("/", "\\\\");
6219           filedat = oldFiles.get(new File(reformatedOldFilename));
6220         }
6221         rewritten.append(Platform.escapeBackslashes(filedat.getFilePath()));
6222         rewritten.append("\"");
6223         cp = ecp + 1; // advance beyond last \" and set cursor so we can
6224                       // look for next file statement.
6225       } while ((ncp = state.indexOf("/*file*/", cp)) > -1);
6226     }
6227     if (cp > 0)
6228     {
6229       // just append rest of state
6230       rewritten.append(state.substring(cp));
6231     }
6232     else
6233     {
6234       System.err.print("Ignoring incomplete Jmol state for PDB ids: ");
6235       rewritten = new StringBuilder(state);
6236       rewritten.append("; load append ");
6237       for (File id : oldFiles.keySet())
6238       {
6239         // add pdb files that should be present in the viewer
6240         StructureData filedat = oldFiles.get(id);
6241         rewritten.append(" \"").append(filedat.getFilePath()).append("\"");
6242       }
6243       rewritten.append(";");
6244     }
6245
6246     if (rewritten.length() == 0)
6247     {
6248       return null;
6249     }
6250     final String history = "history = ";
6251     int historyIndex = rewritten.indexOf(history);
6252     if (historyIndex > -1)
6253     {
6254       /*
6255        * change "history = [true|false];" to "history = [1|0];"
6256        */
6257       historyIndex += history.length();
6258       String val = rewritten.substring(historyIndex, historyIndex + 5);
6259       if (val.startsWith("true"))
6260       {
6261         rewritten.replace(historyIndex, historyIndex + 4, "1");
6262       }
6263       else if (val.startsWith("false"))
6264       {
6265         rewritten.replace(historyIndex, historyIndex + 5, "0");
6266       }
6267     }
6268
6269     try
6270     {
6271       File tmp = File.createTempFile("viewerSession", ".tmp");
6272       try (OutputStream os = new FileOutputStream(tmp))
6273       {
6274         InputStream is = new ByteArrayInputStream(
6275                 rewritten.toString().getBytes());
6276         copyAll(is, os);
6277         return tmp.getAbsolutePath();
6278       }
6279     } catch (IOException e)
6280     {
6281       Console.error("Error restoring Jmol session: " + e.toString());
6282     }
6283     return null;
6284   }
6285
6286   /**
6287    * Populates an XML model of the feature colour scheme for one feature type
6288    * 
6289    * @param featureType
6290    * @param fcol
6291    * @return
6292    */
6293   public static Colour marshalColour(String featureType,
6294           FeatureColourI fcol)
6295   {
6296     Colour col = new Colour();
6297     if (fcol.isSimpleColour())
6298     {
6299       col.setRGB(Format.getHexString(fcol.getColour()));
6300     }
6301     else
6302     {
6303       col.setRGB(Format.getHexString(fcol.getMaxColour()));
6304       col.setMin(fcol.getMin());
6305       col.setMax(fcol.getMax());
6306       col.setMinRGB(jalview.util.Format.getHexString(fcol.getMinColour()));
6307       col.setAutoScale(fcol.isAutoScaled());
6308       col.setThreshold(fcol.getThreshold());
6309       col.setColourByLabel(fcol.isColourByLabel());
6310       col.setThreshType(fcol.isAboveThreshold() ? ThresholdType.ABOVE
6311               : (fcol.isBelowThreshold() ? ThresholdType.BELOW
6312                       : ThresholdType.NONE));
6313       if (fcol.isColourByAttribute())
6314       {
6315         final String[] attName = fcol.getAttributeName();
6316         col.getAttributeName().add(attName[0]);
6317         if (attName.length > 1)
6318         {
6319           col.getAttributeName().add(attName[1]);
6320         }
6321       }
6322       Color noColour = fcol.getNoColour();
6323       if (noColour == null)
6324       {
6325         col.setNoValueColour(NoValueColour.NONE);
6326       }
6327       else if (noColour == fcol.getMaxColour())
6328       {
6329         col.setNoValueColour(NoValueColour.MAX);
6330       }
6331       else
6332       {
6333         col.setNoValueColour(NoValueColour.MIN);
6334       }
6335     }
6336     col.setName(featureType);
6337     return col;
6338   }
6339
6340   /**
6341    * Populates an XML model of the feature filter(s) for one feature type
6342    * 
6343    * @param firstMatcher
6344    *          the first (or only) match condition)
6345    * @param filter
6346    *          remaining match conditions (if any)
6347    * @param and
6348    *          if true, conditions are and-ed, else or-ed
6349    */
6350   public static jalview.xml.binding.jalview.FeatureMatcherSet marshalFilter(
6351           FeatureMatcherI firstMatcher, Iterator<FeatureMatcherI> filters,
6352           boolean and)
6353   {
6354     jalview.xml.binding.jalview.FeatureMatcherSet result = new jalview.xml.binding.jalview.FeatureMatcherSet();
6355
6356     if (filters.hasNext())
6357     {
6358       /*
6359        * compound matcher
6360        */
6361       CompoundMatcher compound = new CompoundMatcher();
6362       compound.setAnd(and);
6363       jalview.xml.binding.jalview.FeatureMatcherSet matcher1 = marshalFilter(
6364               firstMatcher, Collections.emptyIterator(), and);
6365       // compound.addMatcherSet(matcher1);
6366       compound.getMatcherSet().add(matcher1);
6367       FeatureMatcherI nextMatcher = filters.next();
6368       jalview.xml.binding.jalview.FeatureMatcherSet matcher2 = marshalFilter(
6369               nextMatcher, filters, and);
6370       // compound.addMatcherSet(matcher2);
6371       compound.getMatcherSet().add(matcher2);
6372       result.setCompoundMatcher(compound);
6373     }
6374     else
6375     {
6376       /*
6377        * single condition matcher
6378        */
6379       // MatchCondition matcherModel = new MatchCondition();
6380       jalview.xml.binding.jalview.FeatureMatcher matcherModel = new jalview.xml.binding.jalview.FeatureMatcher();
6381       matcherModel.setCondition(
6382               firstMatcher.getMatcher().getCondition().getStableName());
6383       matcherModel.setValue(firstMatcher.getMatcher().getPattern());
6384       if (firstMatcher.isByAttribute())
6385       {
6386         matcherModel.setBy(FilterBy.BY_ATTRIBUTE);
6387         // matcherModel.setAttributeName(firstMatcher.getAttribute());
6388         String[] attName = firstMatcher.getAttribute();
6389         matcherModel.getAttributeName().add(attName[0]); // attribute
6390         if (attName.length > 1)
6391         {
6392           matcherModel.getAttributeName().add(attName[1]); // sub-attribute
6393         }
6394       }
6395       else if (firstMatcher.isByLabel())
6396       {
6397         matcherModel.setBy(FilterBy.BY_LABEL);
6398       }
6399       else if (firstMatcher.isByScore())
6400       {
6401         matcherModel.setBy(FilterBy.BY_SCORE);
6402       }
6403       result.setMatchCondition(matcherModel);
6404     }
6405
6406     return result;
6407   }
6408
6409   /**
6410    * Loads one XML model of a feature filter to a Jalview object
6411    * 
6412    * @param featureType
6413    * @param matcherSetModel
6414    * @return
6415    */
6416   public static FeatureMatcherSetI parseFilter(String featureType,
6417           jalview.xml.binding.jalview.FeatureMatcherSet matcherSetModel)
6418   {
6419     FeatureMatcherSetI result = new FeatureMatcherSet();
6420     try
6421     {
6422       parseFilterConditions(result, matcherSetModel, true);
6423     } catch (IllegalStateException e)
6424     {
6425       // mixing AND and OR conditions perhaps
6426       System.err.println(
6427               String.format("Error reading filter conditions for '%s': %s",
6428                       featureType, e.getMessage()));
6429       // return as much as was parsed up to the error
6430     }
6431
6432     return result;
6433   }
6434
6435   /**
6436    * Adds feature match conditions to matcherSet as unmarshalled from XML
6437    * (possibly recursively for compound conditions)
6438    * 
6439    * @param matcherSet
6440    * @param matcherSetModel
6441    * @param and
6442    *          if true, multiple conditions are AND-ed, else they are OR-ed
6443    * @throws IllegalStateException
6444    *           if AND and OR conditions are mixed
6445    */
6446   protected static void parseFilterConditions(FeatureMatcherSetI matcherSet,
6447           jalview.xml.binding.jalview.FeatureMatcherSet matcherSetModel,
6448           boolean and)
6449   {
6450     jalview.xml.binding.jalview.FeatureMatcher mc = matcherSetModel
6451             .getMatchCondition();
6452     if (mc != null)
6453     {
6454       /*
6455        * single condition
6456        */
6457       FilterBy filterBy = mc.getBy();
6458       Condition cond = Condition.fromString(mc.getCondition());
6459       String pattern = mc.getValue();
6460       FeatureMatcherI matchCondition = null;
6461       if (filterBy == FilterBy.BY_LABEL)
6462       {
6463         matchCondition = FeatureMatcher.byLabel(cond, pattern);
6464       }
6465       else if (filterBy == FilterBy.BY_SCORE)
6466       {
6467         matchCondition = FeatureMatcher.byScore(cond, pattern);
6468
6469       }
6470       else if (filterBy == FilterBy.BY_ATTRIBUTE)
6471       {
6472         final List<String> attributeName = mc.getAttributeName();
6473         String[] attNames = attributeName
6474                 .toArray(new String[attributeName.size()]);
6475         matchCondition = FeatureMatcher.byAttribute(cond, pattern,
6476                 attNames);
6477       }
6478
6479       /*
6480        * note this throws IllegalStateException if AND-ing to a 
6481        * previously OR-ed compound condition, or vice versa
6482        */
6483       if (and)
6484       {
6485         matcherSet.and(matchCondition);
6486       }
6487       else
6488       {
6489         matcherSet.or(matchCondition);
6490       }
6491     }
6492     else
6493     {
6494       /*
6495        * compound condition
6496        */
6497       List<jalview.xml.binding.jalview.FeatureMatcherSet> matchers = matcherSetModel
6498               .getCompoundMatcher().getMatcherSet();
6499       boolean anded = matcherSetModel.getCompoundMatcher().isAnd();
6500       if (matchers.size() == 2)
6501       {
6502         parseFilterConditions(matcherSet, matchers.get(0), anded);
6503         parseFilterConditions(matcherSet, matchers.get(1), anded);
6504       }
6505       else
6506       {
6507         System.err.println("Malformed compound filter condition");
6508       }
6509     }
6510   }
6511
6512   /**
6513    * Loads one XML model of a feature colour to a Jalview object
6514    * 
6515    * @param colourModel
6516    * @return
6517    */
6518   public static FeatureColourI parseColour(Colour colourModel)
6519   {
6520     FeatureColourI colour = null;
6521
6522     if (colourModel.getMax() != null)
6523     {
6524       Color mincol = null;
6525       Color maxcol = null;
6526       Color noValueColour = null;
6527
6528       try
6529       {
6530         mincol = new Color(Integer.parseInt(colourModel.getMinRGB(), 16));
6531         maxcol = new Color(Integer.parseInt(colourModel.getRGB(), 16));
6532       } catch (Exception e)
6533       {
6534         Console.warn("Couldn't parse out graduated feature color.", e);
6535       }
6536
6537       NoValueColour noCol = colourModel.getNoValueColour();
6538       if (noCol == NoValueColour.MIN)
6539       {
6540         noValueColour = mincol;
6541       }
6542       else if (noCol == NoValueColour.MAX)
6543       {
6544         noValueColour = maxcol;
6545       }
6546
6547       colour = new FeatureColour(maxcol, mincol, maxcol, noValueColour,
6548               safeFloat(colourModel.getMin()),
6549               safeFloat(colourModel.getMax()));
6550       final List<String> attributeName = colourModel.getAttributeName();
6551       String[] attributes = attributeName
6552               .toArray(new String[attributeName.size()]);
6553       if (attributes != null && attributes.length > 0)
6554       {
6555         colour.setAttributeName(attributes);
6556       }
6557       if (colourModel.isAutoScale() != null)
6558       {
6559         colour.setAutoScaled(colourModel.isAutoScale().booleanValue());
6560       }
6561       if (colourModel.isColourByLabel() != null)
6562       {
6563         colour.setColourByLabel(
6564                 colourModel.isColourByLabel().booleanValue());
6565       }
6566       if (colourModel.getThreshold() != null)
6567       {
6568         colour.setThreshold(colourModel.getThreshold().floatValue());
6569       }
6570       ThresholdType ttyp = colourModel.getThreshType();
6571       if (ttyp == ThresholdType.ABOVE)
6572       {
6573         colour.setAboveThreshold(true);
6574       }
6575       else if (ttyp == ThresholdType.BELOW)
6576       {
6577         colour.setBelowThreshold(true);
6578       }
6579     }
6580     else
6581     {
6582       Color color = new Color(Integer.parseInt(colourModel.getRGB(), 16));
6583       colour = new FeatureColour(color);
6584     }
6585
6586     return colour;
6587   }
6588
6589   public static void setStateSavedUpToDate(boolean s)
6590   {
6591     Console.debug("Setting overall stateSavedUpToDate to " + s);
6592     stateSavedUpToDate = s;
6593   }
6594
6595   public static boolean stateSavedUpToDate()
6596   {
6597     Console.debug("Returning overall stateSavedUpToDate value: "
6598             + stateSavedUpToDate);
6599     return stateSavedUpToDate;
6600   }
6601
6602   public static boolean allSavedUpToDate()
6603   {
6604     if (stateSavedUpToDate()) // nothing happened since last project save
6605       return true;
6606
6607     AlignFrame[] frames = Desktop.getAlignFrames();
6608     if (frames != null)
6609     {
6610       for (int i = 0; i < frames.length; i++)
6611       {
6612         if (frames[i] == null)
6613           continue;
6614         if (!frames[i].getViewport().savedUpToDate())
6615           return false; // at least one alignment is not individually saved
6616       }
6617     }
6618     return true;
6619   }
6620
6621   // used for debugging and tests
6622   private static int debugDelaySave = 20;
6623
6624   public static void setDebugDelaySave(int n)
6625   {
6626     debugDelaySave = n;
6627   }
6628 }