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