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