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