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