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