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